前端工程化主要解決效率
和質量
的問題;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 倍;