webpack
基本使用
3.解析ES6
高级特性
构建速度优化
优化产出代码(重要)
面试题:
19.ES6 Module 和 CommonJS 区别?为什么只有个ES6 Module 才能让tree-shaking 生效
22.loader 和 plugin 区别?常见的loader和plugin有哪些?
25.babel-polyfill 和 babel-runtime 区别
webpack 安装和基础配置
-
-
- 首先初始化一个npm
npm init -y
会创建一个package.json文件
-
![]()
- 然后本地安装webpack(webpack 一般不会进行全局安装,因为不同的项目对版本不同会产生错误)
- 接着安装webpack-cli (此工具用一在命令中运行webpack)
npm install webpack webpack-cli --save-dev
成功后,会多了一个json文件和 node_modules文件夹。package.json中欧了devDependencies属性
![]()
- 首先初始化一个npm
-
-
-
- 在项目根目录手动创建文件夹src 和 index.html 文件, 在src文件夹内创建 index.js 和hello-world.js 文件
- 目录结构:
- 在项目根目录手动创建文件夹src 和 index.html 文件, 在src文件夹内创建 index.js 和hello-world.js 文件
-

-
-
-
- index.js
import helloWorld from './hello-world' helloWorld() -
hello-world.js
function helloWorld(){ console.log('hello world') } export default helloWorld
- index.js
-
-
-
-
手动在项目根目录创建一个webpack.config.js配置文件
const path = require('path') //管理路径模块的包 //CommonJS 语法 module.exports = { entry:'./src/index.js', //打包入口文件 output:{ filename: 'main.js', //打包输出的文件名 path: path.resolve(__dirname,'dist') //打包输出的路径 } } - 去 package.json 文件scripts里,配置脚本"build":"webpack"
-

-
- 终端输入命令: npm run build 将文件打包,打包的文件会放到 dist/main.js
-
-
- 在index.html 中引入 dist/main.js
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width;initial-scale=1.0"> <title></title> </head> <body> <script src="./dist/main.js"></script> </body> </html>浏览器打开,可以看到成功打印
- 在index.html 中引入 dist/main.js
-
webpack 拆分配置和merge:
开发环境和生产环境下的构建存在差异,为了不重复配置,需要保留通用配置。为了将这些配置合并在一起,需要通过webpack-merge 工具
-
-
- 先需要安装 webpack-merge 的工具
npm install webpack-merge --save-dev - 创建三个文件,分别为:webpack.common.js(公共的配置)、webpack.dev.js(开发环境配置)、webpack.prod.js(生产环境配置)
//webpack.common.js
const path = require('path') //管理路径模块的包 //CommonJS 语法 module.exports = { entry:'./src/index.js', //打包入口文件 output:{ filename: 'main.js', //打包输出的文件名 path: path.resolve(__dirname,'dist') //打包输出的路径 } }// webpack.dev.js
const { merge } = require('webpack-merge'); const common = require('./webpack.common.js'); module.exports = merge(common,{ mode: 'development' })webpack.prod.js
const { merge } = require('webpack-merge') const common = require('./webpack.common.js') module.exports = merge(common,{ mode:'production' }) - 删除之前的webpack.config.js
- 先需要安装 webpack-merge 的工具
-
- webpack 提供了一个可选的本地开发服务,打包的文件不会写入磁盘,只是暂存在内存里的,所以会比较快
- 安装 webpack-dev-server
npm install webpack-dev-server --save-dev
- 配置 package.json, 以便更方便地运行 server:
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --config webpack.prod.js", "serve": "webpack serve --open --config webpack.dev.js" }
执行命令 npm run serve 自动开启服务
- webpack.dev.js 中配置devServer
const { merge } = require('webpack-merge'); const common = require('./webpack.common.js'); module.exports = merge(common,{ mode: 'development', devServer:{ static:'./dist', //静态文件地址 progress: true ,// port: 8080, open:true, //自动打开浏览器 //设置代理 proxy:{ '/api':{ target:'http:localhost:3000', changeOrigin: true, pathRewrite:{ //重写路径 比如'/api/aaa'重写为'http:localhost:3000/aaa' '^/api':'' } } } } }) - 将index.html 文件打包到dist文件夹中,就需要使用HtmlWebpackPlugin插件
npm install html-webpack-plugin --save-dev
配置webpack.common.js文件
const path = require('path') //管理路径模块的包 const HtmlWebpackPlugin = require('html-webpack-plugin') //CommonJS 语法 module.exports = { entry:'./src/index.js', //打包入口文件 output:{ filename: 'main.js', //打包输出的文件名 path: path.resolve(__dirname,'dist') // }, plugins:[ new HtmlWebpackPlugin({ template: path.join(__dirname,'index.html'), filename:'index.html' }) ] }
三、解析ES6
- 首先安装插件
npm install css-loader style-loader less less-loader sass sass-loader --save-dev
处理css、less、scss 文件
const path = require('path') //管理路径模块的包 const HtmlWebpackPlugin = require('html-webpack-plugin') //CommonJS 语法 module.exports = { entry:'./src/index.js', //打包入口文件 output:{ filename: 'main.js', //打包输出的文件名 path: path.resolve(__dirname,'dist') , clean: true }, plugins:[ new HtmlWebpackPlugin({ template: path.join(__dirname,'index.html'), filename:'index.html' }) ], module:{ rules:[ { //用来匹配 .css 结尾的 test:/\.css$/i, //use 执行顺序是:从后往前(即现执行css-loader,再执行style-loader) use:[ 'style-loader', //将 JS 字符串生成 style 节点,最总插入到页面中 'css-loader' //将 CSS 转化成 CommonJS 模块 ] }, { test:/\.less$/i, //use 执行顺序是:从后往前 use:[ 'style-loader', 'css-loader', 'less-loader', ] }, { test:/\.scss$/i, //use 执行顺序是:从后往前 use:[ 'style-loader', 'css-loader', 'sass-loader' ] } ] } } - 处理css3 在不同浏览器兼容性问题
- 安装插件postcss-loader
npm install postcss postcss-loader postcss-preset-env --save-dev
-
在根目录新建一个postcss.config.js 文件,里面的配置如下:
module.exports = { plugins: [ [ "postcss-preset-env", { // Options }, ], ], };
-
webpack.common.js 中的配置postcss-loader
const path = require('path') //管理路径模块的包 const HtmlWebpackPlugin = require('html-webpack-plugin') //CommonJS 语法 module.exports = { entry:'./src/index.js', //打包入口文件 output:{ filename: 'main.js', //打包输出的文件名 path: path.resolve(__dirname,'dist') , clean: true }, plugins:[ new HtmlWebpackPlugin({ template: path.join(__dirname,'index.html'), filename:'index.html' }) ], module:{ rules:[ { //用来匹配 .css 结尾的 test:/\.css$/i, //use 执行顺序是:从后往前(即现执行css-loader,再执行style-loader) use:[ 'style-loader', //将 JS 字符串生成 style 节点,最总插入到页面中 'css-loader', //将 CSS 转化成 CommonJS 模块 'postcss-loader' //css兼容处理 ] }, { test:/\.less$/i, //use 执行顺序是:从后往前 use:[ 'style-loader', 'css-loader', 'less-loader', ] }, { test:/\.scss$/i, //use 执行顺序是:从后往前 use:[ 'style-loader', 'css-loader', 'sass-loader' ] } ] } } -
在
package.json文件中添加browserslist来控制样式的兼容性做到什么程度。{ // 其他省略 "browserslist": ["last 2 version", "> 1%"] }
- 安装插件postcss-loader
或者在增加一个.browserslistrc配置文件
# Browsers that we support last 1 version > 1%
-
- 从Webpack5 开始,我们可以直接使用 asset/resource 或 asset/inline 模块类型来处理图片(webpack4 需要 安装 file-loader、url-loader)
- asset/resource 适合处理大图片,
- asset/inline 适用于小图片或图标,返回的是 Data URL
- 在生产环境下,我们需要考虑小图片 base64编码情况。webpack.prod.js配置如下:
const { merge } = require('webpack-merge') const common = require('./webpack.common.js') module.exports = merge(common,{ mode:'production', module: { rules:[ // 图片考虑 base64 编码的情况 // sset/resource:适用于较大的文件,它会将文件输出到输出目录,并返回一个public URL。 // asset/inline:适用于较小的文件(如Base64编码的图片),它会将文件转换为DataURLs。 { test:/\.(png|jpe?g|gif)$/i, type:'asset', parser:{ dataUrlCondition:{ maxSize: 30 * 1024 //文件大小小于 30 KB时,使用asset/inline模式 } }, generator:{ //输出自定文件名,可以指定存放目录,如不指定的话会打包在根目录下,看起来很混乱 filename:'images/[name].[hash:6][ext]' } } ] } })重新打包后,我们会看到根目录下多一个images 文件夹,存放了一张图片(页面中引入了两张图),而小于30 KB 的图片生成了base64 图片,在代码中引入
-
![]()
-
优点: 减少请求次数; 缺点:体积变得更大
- 输出资源和路径修改
- 打包后的输出资源的路径以及名称都是可以修改的
- 只需要在webpack.prod.js中配置output,webpack.coomon.js 以及 webpack.dev.js不需要配置
const path = require('path') //管理路径模块的包 const { merge } = require('webpack-merge') const common = require('./webpack.common.js') module.exports = merge(common,{ mode:'production', output:{ filename: 'static/js/main.js', //打包输出的文件名和路径 path: path.resolve(__dirname,'dist') , clean: true //自动将上次打包目录资源清空 }, module: { rules:[ // 图片考虑 base64 编码的情况 // sset/resource:适用于较大的文件,它会将文件输出到输出目录,并返回一个public URL。 // asset/inline:适用于较小的文件(如Base64编码的图片),它会将文件转换为DataURLs。 { test:/\.(png|jpe?g|gif)$/i, type:'asset', parser:{ dataUrlCondition:{ maxSize: 8 * 1024 //文件大小小于 8 KB时,使用asset/inline模式 } }, generator:{ //输出自定文件名,可以指定存放目录,如不指定的话会打包在根目录下,看起来很混乱 filename:'static/images/[name].[hash:6][ext]' } } ] } })重新打包后:dist 目录下多出一个static文件夹
-
![]()
- 字体图标
- 如果我们的页面有引入字体图标:例如: fonts/iconfont/ttf ;fonts/iconfont/woff ; fonts/iconfont/woff2
- webpack5 会自动处理,不需要额外的插件配置
- 如果需要指定输出的目录,只需要进行简单配置即可,修改webpack.prod.js文件
rules:[ { //将字体图标资源输 出到 static/media 目录中 // 字体图标命名为: [hash:8][ext][query] //[hash:8] : hash 值取8位 //[ext]: 使用之前的文件扩展名 test:'/\.(ttf| woff2?)$/i', type:'asset/resource', generator:{ filename:'static/media/[hash:8][ext]' } }, ]
- js兼容
现在前端大多数项目都是SPA单页面应用,所以只有一个入口。但有些项目里会产出多个页面,那么webpack 怎么打包成多个入口文件的:
-
- entry 修改
// webpack.commom.js 文件 entry:{ index:'./src/index.js', //打包入口文件 other: './src/other.js'//打包入口文件 }
有几个页面(这里指的是.html文件)
- plugins 插件配置:生成多个html 文件
//webpack.common.js 文件 plugins:[ // 多入口 - 生成 index.html //HtmlWebpackPlugin 将生成一个 HTML 文件,并在其中使用 script 引入一个名为 index.js 的 JS 文件。 new HtmlWebpackPlugin({ template: path.join(__dirname,'index.html'), filename:'index.html', //chunks 表示该页面要引入哪些chunk(即上面入口配置的名称 index 和 other) chunks:['index'] //只引入 index.js 文件,默认不配置会把入口的两个文件全部引入(即index.js 和 other.js) }), // 多入口 - 生成 other.html //HtmlWebpackPlugin 将生成一个 HTML 文件,并在其中使用 script 引入一个名为 other.js 的 JS 文件。 new HtmlWebpackPlugin({ template: path.join(__dirname,'other.html'), filename:'other.html', chunks:['other'] //只引入 other.js 文件 }) ],
- entry 修改
注意:如果chunks 不写的话,会将entry中配置的入口全部引入
如果配置上,就会只引入配置的chunk
-
-
output 输出配置:直接用[name]来代替之前的main,[name] 会根据入口entry中配置的名称自动生成
output:{ //输出路径: static/js filename: 'static/js/[name].js', //打包输出的文件名和路径 path: path.resolve(__dirname,'dist') , clean: true //自动将上次打包目录资源清空 }
-
目前打包后没有css文件,css是当js文件加载时,会创建一个style 标签来添加样式。这样用户体验不好,我们应该是打包单独css文件,然后通过link标签加载性能才更好
css抽离出独立文件的配置:
-
-
- 先下载插件
npm install min-css-extract-plugin --save-dev
-
把weback.common.js中的css处理去掉,dev环境沿用common的处理,prod环境需要进一步配置
// webpack.common.js const path = require('path') //管理路径模块的包 const HtmlWebpackPlugin = require('html-webpack-plugin') //CommonJS 语法 module.exports = { //省略 module:{ rules:[ //去掉之前的样式配置 ] } }
//webpack.dev.js const { merge } = require('webpack-merge'); const common = require('./webpack.common.js'); module.exports = merge(common,{ mode: 'development', module:{ rules:[ { //用来匹配 .css 结尾的 test:/\.css$/i, //use 执行顺序是:从后往前(即现执行css-loader,再执行style-loader) use:[ 'style-loader', //将模块导出内容作为样式,并添加到DOM中(以 style 标签形式插入) 'css-loader', //加载css文件,并且解析import 的 css文件,返回 css代码(然后再经过style-loader 处理) 'postcss-loader' //css兼容处理,使用Postcss加载,并转换成css 文件(然后再经过css-loader处理) ] }, { test:/\.less$/i, //use 执行顺序是:从后往前 use:[ 'style-loader', 'css-loader', 'less-loader', //加载并编译less文件 ] }, { test:/\.scss$/i, //use 执行顺序是:从后往前 use:[ 'style-loader', 'css-loader', 'sass-loader' //加载并编译sass/scss文件 ] } ] } })
-
prod环境配置
const path = require('path') //管理路径模块的包 const { merge } = require('webpack-merge') const common = require('./webpack.common.js') const MiniCssExtractPlugin = require('mini-css-extract-plugin') module.exports = merge(common,{ // 省略 module: { rules:[ // 省略 { //用来匹配 .css 结尾的 test:/\.css$/i, //use 执行顺序是:从后往前(即现执行css-loader,再执行style-loader) use:[ MiniCssExtractPlugin.loader, // 注意:这里不再用 style-loader,这里需要把css单独打包成一个文件 'css-loader', //加载css文件,并且解析import 的 css文件,返回 css代码(然后再经过style-loader 处理) 'postcss-loader' //css兼容处理,使用Postcss加载,并转换成css 文件(然后再经过css-loader处理) ] }, { test:/\.less$/i, //use 执行顺序是:从后往前 use:[ MiniCssExtractPlugin.loader, // 注意:这里不再用 style-loader,这里需要把css单独打包成一个文件 'css-loader', 'less-loader', //加载并编译less文件 ] }, { test:/\.scss$/i, //use 执行顺序是:从后往前 use:[ MiniCssExtractPlugin.loader, // 注意:这里不再用 style-loader,这里需要把css单独打包成一个文件 'css-loader', 'sass-loader' //加载并编译sass/scss文件 ] } ] }, plugins:[ //提取 css 成单独文件 new MiniCssExtractPlugin({ //文件输出目录: static/css //文件名: main.[contenthash:8].css //[contenthash:8] contenthash 是根据抽取到的内容来生成hash。抽取的内容不变hash不变,这样可以命中缓存 filename: "static/css/main.[contenthash:8].css" }) ] })此时重新打包,会发现多一个css文件
-
![]()
- 先下载插件
-
将上面打包出来的样式文件进一步压缩:
-
-
- 首先安装css-minimizer-webpack-plugin:
npm install css-minimizer-webpack-plugin --save-dev
-
在prod环境下引入,并使用
const path = require('path') //管理路径模块的包 const { merge } = require('webpack-merge') const common = require('./webpack.common.js') const MiniCssExtractPlugin = require('mini-css-extract-plugin') const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin') module.exports = merge(common,{ // 省略 plugins:[ //提取 css 成单独文件 new MiniCssExtractPlugin({ //文件输出目录: static/css //文件名: main.[contenthash:8].css //[contenthash:8] contenthash 是根据抽取到的内容来生成hash。抽取的内容不变hash不变,这样可以命中缓存 filename: "static/css/main.[contenthash:8].css" }), //css 压缩 new CssMinimizerWebpackPlugin() ] })重新打包后,生成的main.css被压缩了,代码变成了一行,注视也会被去掉
- 首先安装css-minimizer-webpack-plugin:
-
公共代码指的是:在多个模块或页面中被重复使用的代码。这些代码可能包括:import 引入的 第三方库(比如:lodash)、项目中自定义的工具函数等等
抽离的目的: 抽离后的代码单独打包,可以被浏览器缓存。提升页面的加载效率
使用方法: optimization.splitChunks 分割代码块
-
-
- 在prod环境下进行代码分割
optimization:{ //分割代码块 splitChunks:{ /** * chunks有三个值: * initial: 入口 chunk,只对入口中配置的生成chunk * async: 异步chunk, 只对异步导入的文件进行抽离(import 导入的文件) * all: 全部(包括入口配置的trunk 和 import 导入的文件) */ chunks: 'all', cacheGroups:{ //对于import 引入的 第三方模块单独打包成一个文件(eg: import ~ from lodash) vendor:{ test:/node_modules/, //匹配 node_modules 下的trunk priority: 1,// 权限更高,优先抽离 name:'vendor', //chunk 名称 minSize: 0, //大小限制,达到设置的限制就单拎出来打包 minChunks: 1, // 最少复用过几次,达到设置的次数就单拎出来打包 }, //对于import 引入的 公共的模块(eg:工具函数,多个页面都需要引入) common:{ name:'common', //chunk 名称 priority: 0, // 优先级 minSize: 0, //大小限制 minChunks: 2 // 最少复用过几次 } } } }
-
多入口时,如果设置了chuks,就需要把分割的chunk加进去,否则只会加载设置的trunk,webpack.common.js 配置如下:
plugins:[ // 多入口 - 生成 index.html //HtmlWebpackPlugin 将生成一个 HTML 文件,并在其中使用 script 引入一个名为 index.js 的 JS 文件。 new HtmlWebpackPlugin({ template: path.join(__dirname,'index.html'), filename:'index.html', //chunks 表示该页面要引入哪些chunk(即上面入口配置的名称 index 和 other) chunks:['index','common','vendor'] //引入 index.js common.js vendor.js 文件,默认不配置会把所有的chunk 引入(入口的两个,分组splitChunks 中命中的chunk、) }), // 多入口 - 生成 other.html //HtmlWebpackPlugin 将生成一个 HTML 文件,并在其中使用 script 引入一个名为 other.js 的 JS 文件。 new HtmlWebpackPlugin({ template: path.join(__dirname,'other.html'), filename:'other.html', chunks:['other'] //只引入 other.js 文件,不设置会加载所有的chunk文件(即打包出来的js文件) }) ],
- 在prod环境下进行代码分割
-
测试一下:
-
- index.js 文件
// 引入第三方模块 import _ from 'lodash' //引入 math.js 公共文件 import {sum} from './math'
-
other.js 文件
console.log('我是 other 页面')
// 引入 math.js import {sum} from './math' console.log(sum(100,200)) -
math.js 文件
export const sum = (a,b)=>{ return a+ b }
重新打包后,会多出两个文件:common.js 和 vendor.js 。index.js 和 other.js 体积变小了
- index.js 文件
-
- js中我们可以使用import()函数来异步加载模块,例如:
//引入动态数据 -- 懒加载 setTimeout(()=>{ //import() 函数可以异步加载模块 // import() 返回的是promise对象 import('./dynamic-data').then(res=>{ console.log(res.default.message) }) })
- dynamic-data.js
export default{ message: 'this is a dynamic data'
- 在 index.j中动态引入数据,异步加载模块dynamic-data,重新打包后,dynamic-data会被单独打包(名称被随机命名)
- js中我们可以使用import()函数来异步加载模块,例如:

针对React处理:
-
- 下载包
npm install --save-dev @babel/preset-react
-
common 环境配置:
module:{ //对js兼容处理 rules:[ { test:/\.js$/, use:['babel-loader'], exclude: /node_modules/ //排除node_modules代码 不编译 } ] },
- 下载包
vue 处理:
-
- 安装 vue-loader
npm install --save-dev vue-loader
- common 环境配置:
rules:[ { test:/\.vue$/, loader:'vue-loader' } ]
- 安装 vue-loader
十二、webpack 构建速度优化-- 优化babel-loader
- 启用Babel的缓存机制,使第二次构建速度更快。
- common 环境 配置:
module:{ //对js兼容处理 rules:[ { test:/\.vue$/, loader:'vue-loader' }, { test:/\.js$/, use:['babel-loader?cacheDirectory'], //开启缓存 exclude: /node_modules/ //排除node_modules代码 不编译 } ] },
十三、webpack 构建速度优化-- IgnorePlugin 避免引入无用模块
- 比如 Moment.js 库,只吃很多语言
- import moment from 'moment' 默认会引入所有语言的JS 代码
- 避免引入过多的无用语言,我们可以使用IgnorePlugin(webpack 内置插件),来忽略第三方包指定的目录(让这些目录不要被打包进去)
- prod 环境配置:
plugins:[ //忽略 moment 下的 /locale 目录 new webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/ }) ],
- index.js 文件使用:
// 引入第三方模块 import moment from 'moment' import 'moment/locale/zh-cn' //locale目录被忽略掉后,需要手动引入语言包(自己需要的), console.log(moment().format('ll'),'333333333') //xxxx年x月x日
- noParse避免重复打包
- 向xxx.min.js 文件,本身就已经打包过的,不需要再处理
- prod环境配置:
module: { //忽略对 xxx.min.js 文件的解析处理,本身已经打包过了 noParse:[/react\.min\.js/], rules:[ // 省略 ] }
IgnorePlugin 和 noParse区别:
- IgnorePlugin 是直接不引入,代码中没有。(需要什么,手动引入)
- noParse引入,但不打包
十五、webpack 构建速度优化-- thread-loader 多进程打包
- webpack5 多进程打包 由 happyPack 改成 thread-loader
- 使用时,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行
- 安装
npm install thread-loader --save-dev
- prod环境
rules:[ { test:/\.js$/, exclude: /node_modules/, //排除node_modules代码 不编译 use:[ 'thread-loader', 'babel-loader?cacheDirectory' ] }, ]
- 由于启动进程开销有原因,打包内容少的不建议使用多进程,只有项目很大,打包时间很长时,我们可以考虑开启多进程
十六、webpack 构建速度优化-- 自动刷新与热更新(只使用开发环境)
- 自动刷新:整个网页全部刷新,速度较慢,状态会丢失
- 热更新:网页不刷新,新代码生效,状态不丢失。
- webpack5 中使用webpack-dev-serve 会自动开启热更新
- 如果想开启自动刷新关闭热更新,只需把hot 设为false
module.exports = merge(common,{ mode: 'development', devServer:{ static:'./dist', //静态文件地址 port: 8080, open:true, //自动打开浏览器 hot: false, //是否开启热更新,默认不设置为true //设置代理 proxy:[ { '/api':{ target:'http:localhost:3000', changeOrigin: true, pathRewrite:{ //重写路径 比如'/api/aaa'重写为'http:localhost:3000/aaa' '^/api':'' } } } ] }, // 省略 })
- 自动刷新或者热更新只能用在dev环境,不能用在生产环境
十七、webpack 构建速度优化-- DLLPlugin 动态链接库插件(只使用开发环境)
- 前端框架项目 如 vue、react体积较大、比较稳定、不常升级版本
- 同一个版本只构建一次即可,不用每次都重新打包
- 如果你的项目每次打包很慢的情况,可以采用这个进行优化(如果小的项目,可以不使用)
- 具体的实现:
- webpack 已经内置 DellPlugin 支持
- 先使用DellPlugin 插件 会把vue\react 预先打包出dll文件
- 下次运行的时候,DellReferencePlugin 插件会直接使用上次的 dll 文件
- 代码实现:
- 先要在你的react 项目中单独配置一个webpack.dll.js 文件
const path = require('path') const DllPlugin = require('webpack/lib/DllPlugin') const { srcPath, distPath } = require('./paths') module.exports = { mode: 'development', // JS 执行入口文件 entry: { // 把 React 相关模块的放到一个单独的动态链接库 react: ['react', 'react-dom'] }, output: { // 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称, // 也就是 entry 中配置的 react 和 polyfill filename: '[name].dll.js', // 输出的文件都放到 dist 目录下 path: distPath, // 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react // 之所以在前面加上 _dll_ 是为了防止全局变量冲突 library: '_dll_[name]', }, plugins: [ // 接入 DllPlugin new DllPlugin({ // 动态链接库的全局变量名称,需要和 output.library 中保持一致 // 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值 // 例如 react.manifest.json 中就有 "name": "_dll_react" name: '_dll_[name]', // 描述动态链接库的 manifest.json 文件输出时的文件名称 path: path.join(distPath, '[name].manifest.json'), }), ], } -
package.json 中配置
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "webpack serve --config build/webpack.dev.js", "dll": "webpack --config build/webpack.dll.js" }
-
执行 npm run dll ,会在dist 目录下多出两个文件:react.dll.js、 react.manifest.json
- 在 index.html 中需要引入 react.dll.js 文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <div id="root"></div> <script src="./react.dll.js"></script> </body> </html> - webpack.dev.js 配置
// 第一,引入 DllReferencePlugin const DllReferencePlugin = require('webpack/lib/DllReferencePlugin'); module.exports = merge(webpackCommonConf, { mode: 'development', module: { rules: [ { test: /\.js$/, use: ['babel-loader'], include: srcPath, exclude: /node_modules/ // 第二,不要再转换 node_modules 的代码 }, ] }, plugins: [ // 第三,告诉 Webpack 使用了哪些动态链接库 new DllReferencePlugin({ // 描述 react 动态链接库的文件内容 manifest: require(path.join(distPath, 'react.manifest.json')), }), ] })
- 先要在你的react 项目中单独配置一个webpack.dll.js 文件
-
- 小图片 base64编码
{ test:/\.(png|jpe?g|gif)$/i, type:'asset', parser:{ dataUrlCondition:{ maxSize: 10 * 1024 //文件大小小于 10 KB时,使用asset/inline模式 } }, generator:{ //输出自定文件名,可以指定存放目录,如不指定的话会打包在根目录下,看起来很混乱 filename:'static/images/[name].[hash:6][ext]' } },
- bundle 加hash
-
- 输出的文件名加 contenthash, 会根据文件内容是否改变来生成哈希值。内容改变了hash值就变,内容不变hash不变
output:{ //输出路径: static/js //输出文件名:main.[contenthash:8].js //contenthash: 当文件内容改变了hash值才会变,这样就不用每次打包都生成一个新的 filename: 'static/js/[name][contenthash:8].js', //打包输出的文件名和路径 path: path.resolve(__dirname,'dist') , clean: true //自动将上次打包目录资源清空 },
- 输出的文件名加 contenthash, 会根据文件内容是否改变来生成哈希值。内容改变了hash值就变,内容不变hash不变
-
-
懒加载
-
异步加载,可以通过import()函数引入
-
-
提取公共代码
-
将通过 第三方模块和一些公共工具函数 单独打包
-
采用splitChunks 分割代码块
optimization:{ //分割代码块 splitChunks:{ /** * chunks有三个值: * initial: 入口 chunk,只对入口中配置的生成chunk * async: 异步chunk, 只对异步导入的文件进行抽离(import 导入的文件) * all: 全部(包括入口配置的trunk 和 import 导入的文件) */ chunks: 'all', cacheGroups:{ //对于import 引入的 第三方模块单独打包成一个文件(eg: import ~ from lodash) vendor:{ test:/node_modules/, //匹配 node_modules 下的trunk priority: 1,// 权限更高,优先抽离 name:'vendor', //chunk 名称 minSize: 0, //大小限制,达到设置的限制就单拎出来打包 minChunks: 1, // 最少复用过几次,达到设置的次数就单拎出来打包 }, //对于import 引入的 公共的模块(eg:工具函数,多个页面都需要引入) common:{ name:'common', //chunk 名称 priority: 0, // 优先级 minSize: 0, //大小限制 minChunks: 2 // 最少复用过几次 } } } }
-
-
使用 IngorePlugin 忽略一些无用模块,需要什么自己手动引入
- 例如Moment 库,忽略掉多语言模块,需要什么语言自己手动引入
-
既可以优化速度、也可以优化体积
-
使用 production
- 项目打包环境分成开发和生产环境
- 生产环境 通过mode 设置
module.exports = merge(common,{ mode:'production', }
-
在生产环境下,webpack 会自动压缩代码。不用单独配置压缩代码
- 在生产环境下,对Vue、React等等一些框架会自动删掉调试代码(如开发环境的warning)。这也就是为什么线上的Vue会比开发环境的体积小很多
- 在生产环境下,webpack 会开启 Tree-Shaking。即项目中没有用到的代码 会自动删掉
-
-
- eg: 项目中math.js文件中定义两个函数
export const sum = (a,b)=>{ return a+ b } export const mult = (a,b) =>{ return a * b }
// 注意: ES6 Module 才能让 tree-shaking 生效// CommonJS 就不行在index.js 中值使用了其中一个
import {sum} from './math' console.log(sum(10,20))重新打包后,会发现 mult 函数会被删掉
- 注意:必须使用ES6 Module才能让 tree-shaking 生效,CommonJS 就不行
- eg: 项目中math.js文件中定义两个函数
-
-
- 小图片 base64编码
十九、ES6 Module 和 CommonJS 区别?为什么只有个ES6 Module 才能让tree-shaking 生效
- ES6 Module 是静态引入,编译时就引入
- 必须先引入
- 代码例子:
//提前引入 import apiList from '../config/api.js' //错误的引入方式: if(isDev){ //编译报错,只能静态引入 import apiList from '../config/api.js' }
- Commonjs 是动态引入,执行时才引入
- 可以通过代码动态判断何时引入
- 代码例子:
let api = require('../config/api.js') if(isDev){ // 可以动态引入,执行时引入 api = require('../config/spi.js') }
- webpack 打包过程是编译过程,还没有执行代码
- 所以,只有ES6 Module 才能静态分析,实现Tree-Shaking 。
- 代码层面
- 体积更小加载越快
- 可以通过打包对代码进行压缩、采用Tree-Shaking等 减少体积
- 编辑高级语言或者语法
- 通过构建工具可以是我们很好的使用 ES6的新特性、TS、SCSS等等
- 跨浏览器兼容性和错误检查
- 通过构建工具可以自动处理浏览器兼容性问题,通过polyfill转换成旧版本浏览器可运行的代码
- 体积更小加载越快
- 开发的流程,开发效率
- 统一、高效的开发环境,提高开发效率
- 构建工具可以执行自动化任务,如:代码校验、测试运行、文档生成等。提高开发效率
- 统一的构建流程和产出标准代
- 部署和发布更加简单:
- 打包和构建会生成部署的优化文件,使部署过程更加简单
- 统一、高效的开发环境,提高开发效率
- module(模块): 我们的源码src文件夹下各个文件(webpack 中一切皆是模块),即所有js文件、css文件、图片都是模块
- chunk: 多模块合成的,主要有:entry入口可以定义chunk、异步加载import()函数引入的模块、splitChunk分割的模块
- bundle:最总的输出文件
22、loader 和 plugin 区别?常见的loader和plugin有哪些?
区别:
-
- loader 是模块转换器,
- 默认情况,在遇到import 或者 require 加载模块时,webpack只支持js 和 json文件打包
- 像css、sass 、png 等这些类型的文件,就需要配置对应的loader 进行文件内容的解析
- plugin 是扩展插件, 以扩展webpack的功能 如 HtmlWbpaclPlugin这个插件可以把js和css 文件放到html中
- loader 运行在打包文件之前;plugins在整个编译周期都起作用
- loader 是模块转换器,
常见的loader:
-
- style-loader: 将css 添加到DOM的内部样式标签style 里
- css-loader: 允许将css文件通过 require 方式引入,并返回css代码
- less-loader: 处理less
- sass-loader:处理sass
- postcss-loader: 用postcss处理css兼容写法
- babel-loader: 用 babel来转换es6文件
常见的plugin:
-
- HtmlWebpackPlugin: 简单创建HTML文件,并把打包生成的js模块引入到该html中
- babel 是JS新语法编译工具,不关心模块化
- webpack是 打包构建工具,主要用于模块打包和资源管理、
- babel 和 webpack 通常一起使用
- webpack 可以配置babel作为其中的一个加载器(babel-loader)
- 以便在打包过程中对JS代码进行Babel 转换
在输出配置加上library指定库名
output:{ // lib 文件名 filename:'lodash', // 输出 lib 到 dist 目录 path:path.resolve(__dirname,'dist'),
// 库名,使用时通过lodash 访问导出内容
library:'lodash' ,
libraryTarget:'umd' // 支持模块系统(CommonJS,Window)
}
25、babel-polyfill 和 babel-runtime 区别
- babel-polyfill
- 主要用于添加确实的标准库特性
- 它会修改全局变量,会污染全局
- babel-runtime
- 用于避免全局变量,只注入辅助函数
- 不会污染全局
- 产出第三方lib时,一定要用babel-runtime
webpack中实现懒加载(代码分割),主要是通过动态导入的方式实现的,将应用分割成较小的代码块,只在需要的时候才引入这些代码。提高了加载速度和性能
具体的实现方式有哪些:
-
- 1. 使用 import() 语法来实现动态导入从而创建懒加载模块(默认情况,webpack 会自动分割 调用import() 的代码)
import('./dynamic-data').then(res=>{ console.log(res.default.message) }) import('./dynamic-data2').then(res=>{ console.log(res.default.message) })打包后会自动生成两个文件
- 2. 可以使用魔法注释进行更细的代码分割,指定名称和指定时候合并一个文件
import(/*webpackChunkName:'dynamic-data'*/'./dynamic-data').then(res=>{ console.log(res.default.message) }) import(/*webpackChunkName:'dynamic-data'*/'./dynamic-data2').then(res=>{ console.log(res.default.message) })
此时打包会生成一个名称为:dynamic-data.js文件。可以指定哪些文件放在一个模块里进行打包
- 3. 可以通过 optimization.splitChunks 配置,进一步代码分割
optimization:{ //分割代码块 splitChunks:{ /** * chunks有三个值: * initial: 入口 chunk,只对入口中配置的生成chunk * async: 异步chunk, 只对异步导入的文件进行抽离(import 导入的文件) * all: 全部(包括入口配置的trunk 和 import 导入的文件) */ chunks: 'all', cacheGroups:{ //对于import 引入的 第三方模块单独打包成一个文件(eg: import ~ from lodash) vendor:{ test:/node_modules/, //匹配 node_modules 下的trunk priority: 1,// 权限更高,优先抽离 name:'vendor', //chunk 名称 minSize: 0, //大小限制,达到设置的限制就单拎出来打包 minChunks: 1, // 最少复用过几次,达到设置的次数就单拎出来打包 }, //对于import 引入的 公共的模块(eg:工具函数,多个页面都需要引入) common:{ name:'common', //chunk 名称 priority: 0, // 优先级 minSize: 0, //大小限制 minChunks: 2 // 最少复用过几次 } } } }
可以把第三方的、公共的函数 也进行分割单独打包。
- 4.像 vue 中 异步组件、异步加载路由都是通过import() 方式来实现的
- 1. 使用 import() 语法来实现动态导入从而创建懒加载模块(默认情况,webpack 会自动分割 调用import() 的代码)
没有任何方法来替代Proxy,所以Proxy 不能polyfill 。其他的API都可以用其他功能来模拟
-
- 如 Class 可以用 function 模拟
- 如 Promise 可以用 callback 来模拟
- 但 Proxy 的功能用Object.defineProperty 无法模拟。
-
-
- 优化babel-loader,加缓存
- IgnorePlugin 忽略无用模块
- noParse 避免重复打包
- thread-loader
- 自动刷新和热更新 (只能用于开发环境)
- DllPlugin (只能用于开发环境)
-







浙公网安备 33010602011771号