webpack学习笔记(二)
1 支持es6语法
(注意直接写map等es6语法,由于现代浏览器是支持的,所以可以显示出来,可以执行 npm run build 命令,在生成的文件中,查看map等es6语法是没有被转成es5的)
1.1 首先安装 babel-loader和babel的核心库: npm install -D bable-loader @babel/core
module.exports = { module: { rules: [{ test: /\.js$/, exclude: /node_modules/, //排除这个文件夹下的内容 loader: 'babel-loader', }] } }
1.2 安装 npm install @babel/preset-env -D 需要在特定的平台上执行特定的转码规则,preset-env 说白了就像是按需转码的意思
module.exports = { module: { rules: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: ['@babel/preset-env'] //在这里配置使用loader的配置项 } }] } }
1.3 此时诸如 let等已经转成H5 了,还有一些es6语法不能转过去,比如箭头函数,使用polyfill
npm install --save @babel/polyfill
1.4 在业务代码中引入:
import "@babel/polyfill"
由于会把所有的转换方法打包,所以会导致js文件变的非常大,而我们只用了几个es6的语法
module.exports = { module: { rules: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: [['@babel/preset-env',{ //这里要给preset-env设置参数 所以要放在数组中 useBuiltIns:'usage' //根据业务代码使用的es6语法 来添加对应的转换方法 }]] } }] } }
还可以配置转换的环境,进一步优化缩小打包代码:
//针对各个浏览器的最新的两个版本,以及safari的版本7及以上,针对以上两个条件的兼容情况进行代码转译 { "presets": [ ["env", { "targets": { "browsers": ["last 2 versions", "safari >= 7"] } }] ] }
1.5 由于babel的可配置项特别多,所以可以将其提取出来放在 .babelrc 文件中,然后把options的对象整个放在这个文件中
注意:
1:
Babel 7 的相关依赖包需要加上 @babel
scope。一个主要变化是 presets 设置由原来的 env
换成了 @babel/preset-env
, 可以配置 targets
, useBuiltIns
等选项用于编译出兼容目标环境的代码。其中useBuiltIns
如果设为 "usage"
,Babel 会根据实际代码中使用的ES6/ES7代码,以及与你指定的targets,按需引入对应的 polyfill,而无需在代码中直接引入 import '@babel/polyfill'
,避免输出的包过大,同时又可以放心使用各种新语法特性。
{ "presets": [ ["@babel/preset-env", { "modules": false, "targets": { "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] }, "useBuiltIns": "usage" }] ] }
配置文件中:
{ test:/\.js$/, exclude: __dirname + 'node_modules', use: 'babel-loader' //注意这里要用的是 use 不要直接使用 loadder 否则转化es5 不生效!!!!!!! }
3: 配置打包时报Cannot read property 'bindings' of null 或 Cannot find module '@babel/core'问题
解决:模块中对js的处理配置如下图可解决
或者使用:
{ test:/\.js$/, exclude: /('node_modules')/, use: 'babel-loader' }
2 使用webpack 搭建react的运行环境
2.1 首先安装react npm install react react-dom --save
然后在 .babelrc
{ presets:[ [ "@babel/preset-env",{ targets:{ chrome:"67", }, useBuiltIns: 'usage' } ], "@babel/preset-react" //执行顺序是 从下往上 从右往左 ] }
注意的是:
如果在babel的配置中写了 useBuiltIns: 'usage' (只使用用到了的方法) 则会自动引入 import "@babel/polyfill" 所以不需要在业务代码中再次引入。
3 tree shaking 只打包引用的文件代码,例如定义 build.js:
export const add = (a,b)=>{ console.log(a+b) } export const minus = (a,b)=>{ console.log(a-b) }
在其他文件中引入:import { add } from './build.js'
则在生成的文件中,你会发现即使没有调用 minus方法,也会把整个的build.js 进行打包编译。为了只对使用过的方法进行打包,进行如下操作:
首先明确的是 tree shaking 只对 import { add } from './build.js' 起作用,而对 const add = require ('./build.js') 不起作用
module.exports = { mode: 'development', module: { }, plugins: [ ], optimization: { usedExports: true //在这里进行配置 }, output: { } }
但是这样的话对于 没有 引入具体 函数的: import '@babel/polly-fill' 会不进行打包
所以要在package.json配置文件中 添加: "sideEffects":["@babel/polly-fill"] ,即对不需要使用 tree-shaking 进行操作的文件,放在这里,
但是一般我们不需要在文件中 引入这些的话 ,需要tree-shaking 对所有import的文件进行 按需处理 ,所以这里写成false : "sideEffects":false
但是对于没有具体引出的 css: import './index.css' ,json配置文件中需要重新定义: "sideEffects":["*.css"]
最后 对于开发模式,虽然能够识别,但是便于开发调试,没有删掉未引用的代码,在上线的模式 中:
mode: 'development', //开发环境 mode: 'production' // 上线环境 // tree-shaking 已经把 optimization 写好了 // 所以删除 // optimization: { // usedExports: true //在这里进行配置 // },
然后搜索打包后的js文件,发现没有引入的方法不再有了。
4 对webpack的配置文件进行模式区分:新建两个配置文件,其中 webpack.dev.js 的 mode是 mode: 'development'
{ "scripts":{ "dev":"webpack-dev-server --config webpack.dev.js", "build":"webpack --config webpack.prod.js" } }
4.1 由于两个文件重复性很大,所以把共有的代码放在新建的文件中: webpack.common.js;
这样就会有三个配置文件 分别是 共性的代码 webpack.common.js;开发的配置文件 webpack.dev.js;上线的文件 webpack.prod.js。
然后使用webpack-merge进行文件的合并: npm install webpack-merge -D
const merge = require('webpack-merge'); const commonConfig = require('./webpack.common.js'); const devConfig = { mode:'', devtool:'', devServer:{ } } module.exports = merge(commonConfig,devConfig); //这样使用 merge 合并后导出 commonConfig 和 devConfig
如果把这三个配置文件放在build文件夹下,则config.json中相应的修改:
"dev":"webpack-dev-server --config ./build/webpack.dev.js"
同样配置文件中也要修改:
{ //CleanWebpackPlugin插件会默认当前文件为根路径, //所以需要用root参数,配置当前的根路径 plugins:[ new CleanWebpackPlugin(['dist'],{ //dist路径是在root相对下的路径 root:path.resolve(__dirname,'../') }) ], output:{ filename:'[name].js', path:path.resolve(__dirname,'../dist') //这做修改里也要 } }
5. code Splitting 即代码拆分 比如要提取出公共的 lodash代码
5.1 首先安装 npm install lodash --save 该库可以用来操作一些字符串,
比如:
import _ from 'lodash'; var element = document.createElement('div'); element.innerHTML = _.join(['Dell', 'Lee'], '-'); document.body.appendChild(element);
然后在配置文件中,在和entry,module等并列的位置:
optimization:{ splitChunks:{ chunks:'all' } }
就可以自动打包出vendor.js;
5.2 异步引入的js库
function getComponent() { return import('lodash').then(({ default: _ }) => { var element = document.createElement('div'); element.innerHTML = _.join(['Dell', 'Lee'], '-'); return element; }) } getComponent().then(element => { document.body.appendChild(element); });
但是打包不支持,所以安装: npm install babel-plugin-dynamic-import-webpack //动态引入import
因为引入了新的babel,所以要在babelrc文件中修改:
{ presets: [ [ "@babel/preset-env", { targets: { chrome: "67", }, useBuiltIns: 'usage' } ], "@babel/preset-react" ], plugins: ["dynamic-import-webpack"] //修改了这里 }
代码分割和webpack无关,
webpack 中实现代码分割,两种方式:
1. 同步代码: 只需要在webpack.common.js 中做 optimization 的配置
2. 异步代码: 无需做任何配置需要安装上述插件;
以上promise代码还可以简化成async模式:
async function getComponent() { const { default:_ } = await import( /* webpackChunkName:"lodash" */ 'lodash' ); var element = document.createElement('div'); element.innerHTML = _.join(['Dell', 'Lee'], '-'); return element; } getComponent().then(element => { document.body.appendChild(element); });
5.3 以上不是官方插件,所以不要看上面的了,看下面的官方插件(在babel的官网上):
首先安装: npm install --save-dev @babel/plugin-syntax-dynamic-import
plugins: ["@babel/plugin-syntax-dynamic-import"]
然后使用方式为:
function getComponent() { return import(/* webpackChunkName:"lodash" */ 'lodash').then(({ default: _ }) => { var element = document.createElement('div'); element.innerHTML = _.join(['Dell', 'Lee'], '-'); return element; }) } getComponent().then(element => { document.body.appendChild(element); });
config文件中:
optimization: { splitChunks: { chunks: 'all', cacheGroups: { vendors:false , default:false } } }
更多配置:
optimization: { splitChunks: { chunks: 'all',//同步异步全都打包 minSize: 30000,//打包的库或者文件必须大于这个字节才会进行拆分 minChunks: 1,//规定当模块在生成的js文件(trunk)中被调用过多少次的时候再进行拆分 maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~',//如果不写filename 默认名字 组名~[name] name: true, cacheGroups: {//缓存组,因为需要打包完成之后,在把所有要拆分的代码合并拆分,所以先要缓存 vendors: { test: /[\\/]node_modules[\\/]/, //如果上面chunks定为all,就是找到所有的import文件,看他是不是调用于 node_modules 文件夹 是的话就拆分 priority: -10,//优先级 比如同时符合vender 和 default 这个优先级高 所以存在这里 filename: 'vendors.js', //拆分后打包的文件名字 }, default: {//像文件中 import进来的文件 如果不在 node_modules文件夹中 则走默认组,打包出的文件名字是 common.js priority: -20, reuseExistingChunk: true,//比如a.js 引用了 b.js;如果b.js在之前已经被拆分过,则这里不再对其进行拆分 filename: 'common.js' } } } }