前端工程化主要解决效率
和质量
的问题;webpack是一种实现前端工程化的有效方案。
基本配置思路图
webpack相似一个加工处理器,经过灵活的配置文件实现复杂需求;主要配置有入口、出口、装在器、插件
资料
moudle/chunk/bundle:javascript
自动化处理js,css,html,图片等静态资源
主要让javascript适应宿主环境,竟可能的压缩体积和复用
ployfill : 全局垫片,注入全局对象,污染全局
runtime : 局部垫片,按需局部注入,不会污染全局
presetscss
// .babelrc { "presets": [ ["@babel/preset-env", { "useBuiltIns": "usage|entry|false" // usage 按需加载polyfill }] ] }
browerslist : 兼容到目标浏览器标准html
// ts.config.json { "compilerOptions": { "module": "commonjs", "target": "es5" }, "exclude": \["./node\_modules"\] }
style-loader (合并style标签,transform插入页面前对其修改)
css-loader (css模块化功能)(importLoaders
解决css中@import未能处理loader的问题)
mini-css-extract-plugin(提取css至单独文件)vue
less-loader
sass-loader
stylus-loaderjava
postcss
autoprefixer : 兼容浏览器样式
postcss-preset-env : 可设置目标css规范
cssnano是一个强大的 PostCss 插件,在 CSS 压缩优化中会常常被用到,它有别于常规的 CSS 压缩工具只是去除空格注释,还支持根据 CSS 语法解析结果智能压缩代码node
html-webpack-plugin : 打包并处理html文件至目标文件夹react
file-loader : 容许js加载其余资源;如, 图片,字体,媒体 等
url-loader : 在file-loader基础上封装;如, 容许将图片转成base64格式webpack
tips: 注意3.0版本
esMoudle属性
svg-url-loader 的工做原理相似于 url-loader
,除了它利用 URL encoding 而不是 Base64 对文件编码。
能够借助img-webpack-loader来对使用到的图片进行优化。它支持 JPG、PNG、GIF 和 SVG 格式的图片。git
// webpack.config.js module.exports = { module: { rules: [ { test: /\.(jpe?g|png|gif|svg)$/, loader: 'image-webpack-loader', // 这会应用该 loader,在其它以前 enforce: 'pre' } ] } };
经过enforce: 'pre'
咱们提升了 img-webpack-loader 的优先级,保证在url-loader
和svg-url-loader
以前就完成了图片的优化。
另外img-webpack-loader默认的配置就已经适用于平常开发图片的压缩优化需求了,可是若是你想更进一步去配置它,参考插件选项。要选择指定选项,请查看国外牛人写的一个图像优化指南。
{ // 文件解析 test: /\.(eot|woff|ttf|woff2|appcache|mp4|pdf)(\?|$)/, loader: 'url-loader', query: { // 这么多文件,ext不一样,因此须要使用[ext] name: 'assets/[name].[hash:7].[ext]' } },
要导入 CSV,TSV 和 XML,可使用csv-loader和xml-loader。
{ test: /\.(csv|tsv)$/, use: [ 'csv-loader' ] }, { test: /\.xml$/, use: [ 'xml-loader' ] }
通常静态资源上线的时候都会放到 CDN,假设咱们的 CDN 域名和路径为:http://bd.xxx.com/img/
,这时候只须要修改output.publicPath
便可
module.exports = { //.. output: { publicPath: 'http://bd.xxx.com/img/' } //.. };
jsx
hot-module-replacement-plugin
// babelrc presets: [ // 添加 preset-react require.resolve('@babel/preset-react'), [require.resolve('@babel/preset-env'), {modules: false}] ],
// index.js if (module.hot) { module.hot.accept(err => { if (err) { console.error('Cannot apply HMR update.', err); } }); }
webapck-dev-server( 错误遮罩,代理请求,热更新,重定向)
webpack.HotModuleReplacementPlugin 插件来开启全局的 HMR 能力
// webpack.config.js const path = require('path'); module.exports = { entry: './src/index.js', devServer: { contentBase: path.join(__dirname, 'dist'), port: 9000, // 开启 hmr 支持 hot: true, overlay: true, // 错误遮罩 proxy: { // 接口代理 ... } }, plugins: [ // 添加 hmr plugin new webpack.HotModuleReplacementPlugin() ] };
// 在入口文件index.js最后添加以下代码 if (module.hot) { // 通知 webpack 该模块接受 hmr module.hot.accept(err => { if (err) { console.error('Cannot apply HMR update.', err); } }); }
devtool开启sourceMap方便定位错误 : evl-source-map(开发) / source-map(生产)
module.exports = { ... devtool: 'evl' }
{ test: /\.js$/, loader: 'eslint-loader', enforce: 'pre', include: [path.resolve(__dirname, 'src')], // 指定检查的目录 options: { // 这里的配置项参数将会被传递到 eslint 的 CLIEngine formatter: require('eslint-friendly-formatter') // 指定错误报告的格式规范 } }
const StyleLintPlugin = require('stylelint-webpack-plugin'); module.exports = { // ... plugins: [new StyleLintPlugin(options)] // ... };
- 多入口分割思路: 主业务代码 + 公共依赖 + 第三方依赖 + webapck运行代码
- 单入口分割思路: 主业务代码 + 异步依赖 + 第三方依赖 + webpack运行代码
因为 Webpack 作到了开箱即用,因此splitChunks
是有默认配置的:
module.exports = { // ... optimization: { splitChunks: { chunks: 'async', // 三选一: "initial" | "all" | "async" (默认) minSize: 30000, // 最小尺寸,30K,development 下是10k,越大那么单个文件越大,chunk 数就会变少(针对于提取公共 chunk 的时候,无论再大也不会把动态加载的模块合并到初始化模块中)当这个值很大的时候就不会作公共部分的抽取了 maxSize: 0, // 文件的最大尺寸,0为不限制,优先级:maxInitialRequest/maxAsyncRequests < maxSize < minSize minChunks: 1, // 默认1,被提取的一个模块至少须要在几个 chunk 中被引用,这个值越大,抽取出来的文件就越小 maxAsyncRequests: 5, // 在作一次按需加载的时候最多有多少个异步请求,为 1 的时候就不会抽取公共 chunk 了 maxInitialRequests: 3, // 针对一个 entry 作初始化模块分隔的时候的最大文件数,优先级高于 cacheGroup,因此为 1 的时候就不会抽取 initial common 了 automaticNameDelimiter: '~', // 打包文件名分隔符 name: true, // 拆分出来文件的名字,默认为 true,表示自动生成文件名,若是设置为固定的字符串那么全部的 chunk 都会被合并成一个 /** =========核心配置cacheGroups======= **/ cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, // 正则规则,若是符合就提取 chunk priority: -10 // 缓存组优先级,当一个模块可能属于多个 chunkGroup,这里是优先级 }, default: { minChunks: 2, priority: -20, // 优先级 reuseExistingChunk: true // 若是该chunk包含的modules都已经另外一个被分割的chunk中存在,那么直接引用已存在的chunk,不会再从新产生一个 } } } } };
splitChunks默认配置对应的就是 chunk 生成的第二种状况:经过写代码时主动使用import()或者require.ensure来动态加载。
Tips:除了 JavaScript,
splitChunks
也适用于使用
mini-css-extract-plugin插件的 css 配置。
Webpack 打包时,除了模块代码以外,Webpack 的 bundle 中还包含了 Runtime(运行时),这部分代码是一小段用来管理模块执行和加载的代码。
// webpack.config.js module.exports = { optimization: { runtimeChunk: true } }; // 分离webapck 运行时代码
官方版本
社区版本
在mode=production
下,Webpack 会自动压缩代码,咱们能够自定义本身的压缩工具,这里推荐 terser-webpack-plugin,它是 Webpack 官方维护的插件
const TerserPlugin = require('terser-webpack-plugin'); module.exports = { optimization: { minimizer: [new TerserPlugin({ // 使用 cache,加快二次构建速度 cache: true, terserOptions: { comments: false, compress: { // 删除无用的代码 unused: true, // 删掉 debugger drop_debugger: true, // eslint-disable-line // 移除 console drop_console: true, // eslint-disable-line // 移除无用的代码 dead_code: true, // eslint-disable-line parallel: true // 多线程 } } })] } };
做用域提高(Scope Hoisting)是指 webpack 经过 ES6 语法的静态分析,分析出模块之间的依赖关系,尽量地把模块放到同一个函数中。
Tips:其实 webpack 4 中,在 production 模式下已经根据大多数项目的优化经验作了通用的配置,相似 Tree-Shaking、Scope Hoisting 都是默认开启的,并且最新版本的 Webpack 使用的压缩工具就是 terser-webpack-plugin。
在 Webpack 中,Tree-Shaking 是须要配合mode=production
来使用的,这是由于 Webpack 的 Tree-Shaking 实际分了两步来实现
import
引入;uglifyjs-webpack-plugin
或terser-webpack-plugin
)进行删除,这些工具只在mode=production
中会被使用。// babelrc presets: [ [require.resolve('@babel/preset-env'), {modules: false}] // 防止babel转义模块,致使tree-shaking实效 ],
首先咱们的 CSS 文件应该是导出到单独的 CSS 文件中,而不要直接打包到 JavaScript 文件中,而后经过style-loader
的 addStyles
方法添加进去,导出 CSS 文件就须要使用mini-css-extract-plugin这个插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { plugins: [ new MiniCssExtractPlugin({ filename: '[name].css', chunkFilename: '[name].[contenthash:8].css' }) ], module: { rules: [ { test: /\.css$/, use: [ { loader: MiniCssExtractPlugin.loader, options: { publicPath: '../', hmr: process.env.NODE_ENV === 'development' } }, 'css-loader' ] } ] } };
cssnano是基于 postcss 的一款功能强大的插件包,它集成了 30 多个插件,只须要执行一个命令,就能够对咱们的 CSS 作多方面不一样类型的优化,
在 Webapck 中,css-loader 已经集成了 cssnano,咱们还可使用optimize-css-assets-webpack-plugin来自定义 cssnano 的规则。optimize-css-assets-webpack-plugin 是一个 CSS 的压缩插件,默认的压缩引擎就是 cssnano。咱们来看下怎么在 Webpack 中使用这个插件:
// webpack.config.js const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); module.exports = { plugins: [ new OptimizeCssAssetsPlugin({ assetNameRegExp: /\.optimize\.css$/g, cssProcessor: require('cssnano'), // 这里制定了引擎,不指定默认也是 cssnano cssProcessorPluginOptions: { preset: ['default', {discardComments: {removeAll: true}}] }, canPrint: true }) ] };
若是咱们的项目中小图片特别多,例若有不少 icon 类的图标,这时候则推荐使用雪碧图(CSS Sprite)来合并这些小图到一张大图中,而后使用background-position
来设置图片的位置,经过这样的方式能够节省屡次小图片的请求。
// postcss.config.js const postcssSprites = require('postcss-sprites'); module.exports = { plugins: [ postcssSprites({ // 在这里制定了从哪里加载的图片被主动使用css sprite // 能够约定好一个目录名称规范,防止所有图片都被处理 spritePath: './src/assets/img/' }) ] }; // tips: 雪碧图的位置时根据图片原图的位置而肯定的,在多端适配问题上可能会有问题
对于大图片来讲,可使用image-webpack-loader来压缩图片,image-webpack-loader 它支持 JPG、PNG、GIF 和 SVG 格式的图片,所以咱们在碰到全部这些类型的图片都会使用它。
webpack.mode介绍
浏览器只会在文件名发生改变(或者浏览器缓存策略失效)时才会请求网络。在使用 Webpack 构建项目的时候,一样能够作到自动更新,但 Webpack 使用的不是版本号,而是指定哈希值(hash),Webpack 的 hash 值有三种:
hash
:每次编译 Compilation 对象的 hash,全局一致,跟单次编译有关,跟单个文件无关,不推荐使用;chunkhash
:chunk 的 hash,根据不一样的 chunk 及其包含的模块计算出来的 hash,chunk 中包含的任意模块发生变化,则 chunkhash 发生变化,推荐使用;contenthash
:CSS 文件特有的 hash 值,是根据 CSS 文件内容计算出来的,CSS 发生变化则其值发生变化,推荐 CSS 导出中使用。const PrepackWebpackPlugin = require('prepack-webpack-plugin').default; const configuration = {}; module.exports = { // ... plugins: [ new PrepackWebpackPlugin(configuration) ] };
使用 resolve.alias
减小查找过程
使用 resolve.extensions
优先查找
合理配置 rule 的查找范围,设置inclue,exclude范围
module.exports = { resolve: { // 顺序从前到后查找 extensions: \['.js', '.jsx', '.ts', '.tsx', '.vue'\], // 使用 alias 把导入 react 的语句换成直接使用单独完整的 react.min.js 文件, // 减小耗时的递归解析操做 alias: { react: path.resolve(__dirname, './node_modules/react/dist/react.min.js'), '@assets': resolve('./src/assets') } }, // 排除不须要解析的模块 module: { noParse: /node_modules\/(jquey\.js)/; } };
thread-loader : 是针对 loader 进行优化的,它会将 loader 放置在一个 worker 池里面运行,以达到多线程构建
// webpack.config.js module.exports = { module: { rules: [ { test: /\.js$/, include: path.resolve('src'), use: [ 'thread-loader' // 你的高开销的loader放置在此 (e.g babel-loader) ] } ] } };
happypack : 利用多线程模型来提升构建速度 / 支持列表
// webpack.config.js const os = require('os'); const HappyPack = require('happypack'); // 根据 cpu 数量建立线程池 const happyThreadPool = HappyPack.ThreadPool({size: os.cpus().length}); module.exports = { module: { rules: [ { test: /\.js$/, use: 'happypack/loader?id=jsx' }, { test: /\.less$/, use: 'happypack/loader?id=styles' } ] }, plugins: [ new HappyPack({ id: 'jsx', // 多少个线程 threads: happyThreadPool, loaders: ['babel-loader'] }), new HappyPack({ id: 'styles', // 自定义线程数量 threads: 2, loaders: ['style-loader', 'css-loader', 'less-loader'] }) ] };
预先编译和打包不会变更存在的文件,在业务代码中直接引入,加快 Webpack 编译打包的速度,可是并不能减小最后生成的代码体积。
wepack.DllPlugin
add-asset-html-webpack-plugin可将dll处理文件自动插入html中
// webpack.config.dll.js const webpack = require('webpack'); // 这里是第三方依赖库 const vendors = ['react', 'react-dom']; module.exports = { mode: 'production', entry: { // 定义程序中打包公共文件的入口文件vendor.js vendor: vendors }, output: { filename: '[name].[chunkhash].js', // 这里是使用将 verdor 做为 library 导出,而且指定全局变量名字是[name]_[chunkhash] library: '[name]_[chunkhash]' }, plugins: [ new webpack.DllPlugin({ // 这里是设置 mainifest.json 路径 path: 'manifest.json', name: '[name]_[chunkhash]', context: __dirname }) ] };
// webpack.config.js const webpack = require('webpack'); module.exports = { output: { filename: '[name].[chunkhash].js' }, entry: { app: './src/index.js' }, plugins: [ new webpack.DllReferencePlugin({ context: __dirname, // 这里导入 manifest配置内容 manifest: require('./manifest.json') }) ] };
terser-webpack-plugin : 开启多线程(parallel)和缓存(cache)
const TerserPlugin = require('terser-webpack-plugin'); module.exports = { optimization: { minimizer: [ new TerserPlugin({ cache: true, // 开启缓存 parallel: true // 多线程 }) ] } };
如babel-loader中cacheDirectory
rules: [ { test: /\.js$/, loader: 'babel-loader', options: { cacheDirectory: true }, // 排除路径 exclude: /node_modules/, // 查找路径 include: [path.resolve('.src')] } ];
适当选择sourceMap的devtool值
切换一些 loader 或者插件,好比: fast-sass-loader能够并行处理 sass 文件,要比 sass-loader 快 5~10 倍;