1.自動清理構建目錄產物css
rm -rf ./dist && webpack 或者 rimraf ./dist && webpack
clean-webpack-plugin(插件) { plugins: [ new CleanWebpackPlugin() ] }
create-react-app中 const fs = require('fs-extra'); fs.emptyDirSync(paths.appBuild); //確保目錄爲空。若是目錄不爲空,則刪除目錄內容。若是該目錄不存在,則會建立該目錄。目錄自己不會被刪除。
2. 自動補齊css3前綴html
postcss-loader cra中 { // Options for PostCSS as we reference these options twice // Adds vendor prefixing based on your specified browser support in // package.json loader: require.resolve('postcss-loader'), options: { // Necessary for external CSS imports to work // https://github.com/facebook/create-react-app/issues/2677 ident: 'postcss', plugins: () => [ require('postcss-flexbugs-fixes'), require('postcss-preset-env')({ autoprefixer: { flexbox: 'no-2009', }, stage: 3, }), // Adds PostCSS Normalize as the reset css with default options, // so that it honors browserslist config in package.json // which in turn let's users customize the target behavior as per their needs. postcssNormalize(), ], sourceMap: isEnvProduction && shouldUseSourceMap, }, }
3. 移動端px 自動轉化成remnode
px2rem-loader
.page {
font-size: 12px; /*no*/
width: 375px; /*no*/
height: 40px;
}
後面有 /*no*/這種註釋語法會不進行 rem 的轉換
目前官方推薦咱們使用vw、vh進行適配,postcss-px-to-viewportreact
4. 靜態資源內聯(代碼層面、請求層面)webpack
代碼層面: 初始化腳本;上報相關打點;css內聯避免頁面閃動;css3
請求層面:減小http請求數(url-loader)git
raw-loader 內聯html,js腳本
index.html
${ require('raw-loader!./meta.html')} // html-webpack-plugin 默認使用ejs引擎的語法,能夠用${}; 內聯loader也是從右到左執行
css內聯es6
1⃣️藉助style-loadergithub
2⃣️html-inline-css-webpack-pluginweb
5.多頁面應用打包通用方案
動態獲取entry和設置html-webpack-plugin數量 利用glob.sync (require('glob'))
function setMPA(){
let entry = {}
let HtmlWebpackPlugin = []
let files = glob.sync(path.join(__dirname, './src/*/index.js'))
Object.values(files).map( (file) => {
const match = file.match(/src\/(.*)\/index.js/)
const pageName = match && match[1]
entry[pageName] = file
HtmlWebpackPlugin.push( new HtmlWebpackPlugin({
...
}))
}
)
return {
entry,
HtmlWebpackPluguin
}
} { entry: setMPA().entry
}
6. 提取頁面公共資源(基礎庫等的分離也一般用splitChunksPlugin)
7. 代碼分割和動態import
1⃣️ 抽離相同代碼到一個共享快
2⃣️ 腳本懶加載,使得初始下載的代碼更小(require.ensure比較老)
能夠使用react-loadable或者本身封住個異步加載的函數,關鍵代碼爲 const { default: component } = await import('./index.js')
8. webpack打包庫和組件;(支持AMD、CJS、ESM)模塊引入
實現一個大整數加法庫的打包(33課程)
1⃣️ npm init -y
2⃣️ npm i webpack webpack-cli -D
3⃣️ 建立 webpack.config.js、index.js、src/index.js
具體借鑑(https://github.com/geektime-geekbang/geektime-webpack-course/tree/master/code/chapter03/large-number)
9. 構建配置包設置
待續
10. 初級分析:使用webpack內置的stats(信息顆粒度粗,做用不大)
11. 速度分析:speed-measure-webpack-plugin
11. 體積分析:webpack-bundle-analyzer
優化:
1⃣️ 使用高版本的webpack、node.js
2⃣️ 多進程/多實例構建
HappyPack => webpack3, 對於多個實例,經過ID去匹配
// @file webpack.config.js exports.plugins = [ new HappyPack({ id: 'jsx', threads: 4, loaders: [ 'babel-loader' ] }), new HappyPack({ id: 'styles', threads: 2, loaders: [ 'style-loader', 'css-loader', 'less-loader' ] }) ]; exports.module.rules = [ { test: /\.js$/, use: 'happypack/loader?id=jsx' }, { test: /\.less$/, use: 'happypack/loader?id=styles' }, ]
webpack4 採用官方的thread-loader (workers 默認是require('os').cpus().length - 1)
3⃣️ 多進程並行壓縮代碼(前兩種主要針對於webpack3,不支持對es6代碼(壓縮中)進行壓縮; 第三種是webpack4的,支持對es6模塊進行壓縮)
4⃣️ 預編譯資源模塊(相比於以前講的html-webpack-externals-plugin, 每次基礎庫都要有cdn, splitChunks 每次編譯都會解析;會更好)
https://webpack.docschina.org/plugins/dll-plugin/
webpack.dll.js //"dll": "webpack --config webpack.dll.js" const path = require('path'); const webpack = require('webpack'); module.exports = { entry: { library: [ 'react', 'react-dom' ] }, output: { filename: '[name]_[chunkhash].dll.js', path: path.join(__dirname, 'build/library'), library: '[name]' }, plugins: [ new webpack.DllPlugin({ name: '[name]_[hash]', path: path.join(__dirname, 'build/library/[name].json') }) ] };
// webpack.config.js
new webpack.DllReferencePlugin({
manifest: require('./build/library/library.json') // 這個文件夾不在打包的壓縮文件夾之下
})
5⃣️ 充分利用緩存提高二次構建速度
babel-loader?cacheDirectory=true terser-webpack-plugin cache: true hard-source-webpack-plugin { plugins: [ new HardSourceWebpackPlugin() ] } 這三個一塊兒使用
6⃣️ 減小構建目標
babel-loader 設置 include: paths.appSrc
7⃣️ tree-shaking
const path = require('path'); const TerserPlugin = require('terser-webpack-plugin'); module.exports = { entry: { 'tool_sensors': path.resolve(__dirname, './src/index.js'), 'tool_sensors.min': path.resolve(__dirname, './src/index.js') }, output: { filename: '[name].js', library: 'initSensor', libraryTarget: 'umd', libraryExport: 'default' }, mode: 'production', optimization: { // usedExports: true, //mode: 'production'默認爲true; tree-shaking須要開啓 minimize: true, minimizer: [ new TerserPlugin({ include: /\.min\.js$/, }) ] }, module:{ rules: [ { test: /\.m?js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { modules: false } ] ], plugins: ['@babel/plugin-transform-runtime'] } } } ] } }
目前purifycss已經不維護,使用purgecss-webpack-plugin、mini-css-extract-plugin實現tree-shaking的效果
{ plugins: [ new MiniCssExtractPlugin({ filename: "[name].css", }), new PurgecssPlugin({ paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }), // nodir : 只匹配文件 }), ]
8⃣️使用webpack進行圖片壓縮
9⃣️ 使用動態Polyfill服務
原理:識別userAgent,下發不一樣的polyfill
🔟 scope-hoisting
現象:構建後的代碼存在大量的閉包代碼
`
總結:
12. 自定義loader(第70節)
執行順序, 串行執行,從右到左(compose = (f, g) => (...args) => f(g(...args))
loader-runner 高效調試loader (做爲webpack的依賴,webpack中使用它執行loader)https://github.com/geektime-geekbang/geektime-webpack-course/tree/master/code/chapter07/raw-loader
// 簡單的loader module.exports = function(source) { const json = JSON.stringify(source) .replace('foo', '') .replace(/\u2028/g, '\\u2028') .replace(/\u2029/g, '\\u2029'); return `export default ${json}`; }
const { runLoaders } = require('loader-runner'); const fs = require('fs'); const path = require('path'); runLoaders({ resource: path.join(__dirname, './src/demo.txt'), loaders: [ { loader: path.join(__dirname, './src/raw-loader.js') } ], context: { minimize: true }, readResource: fs.readFile.bind(fs) }, (err, result) => { err ? console.log(err) : console.log(result); });
經過loader-utils來獲取配置項
const loaderUtils = require('loader-utils') const options = loaderUtils.getOptions(this)
若是回傳一個參數能夠直接用 return
若是回傳多個參數能夠採用 this.callback(null, data1, data2) // result.result === [daga1, data2]
異步的loader (經過 this.async() 產生一個函數進行後續調用)
const callback = this.async() // fs.readFile(path.join(__dirname, './async.txt'), 'utf-8', (err, data) => { if (err) { callback(err, ''); } callback(null, data); });
loader使用緩存(webpack中默認開啓)
能夠使用this.cacheable(false)關閉緩存
緩存條件:loader的結果在相同的輸入下有相同的輸出(有依賴的loader沒法使用緩存)
13 自定義plugin(插件沒有像loader同樣的獨立運行環境,只能運行在webpack)
// 最簡單的plugin // 必須有apply方法 class MyPlugin { apply(compiler){ compiler.hooks.done.tap('My Plugin', (stats) => { console.log('hello world') }) } } module.exports = { configureWebpack: { plugins: [ new MyPlugin() ] } }
1⃣️ 插件的錯誤處理
參數校驗階段能夠直接throw throw new Error('Error Message') 經過compilation對象的warnings和errors接收 compilation.warnings.push('warning'); compilation.errors.push('error');
2⃣️ 經過compilation進行文件寫入
// 文件寫入須要使用webpack-sources const {RawSource} = require('webpack-sources'); module.exports = class DemoPlugin{ constructor(options){ this.options = options } apply(compiler){ const {name} = this.options; compiler.hooks.emit.tapAsync('DemoPlugin', (compilation, callback) => {
//webpack在打包時會生成對應的文件,name爲對應的路徑 compilation.assets[name] = new RawSource('demo')
callback(); }) } }
3⃣️ 實戰:將打包生成的dist目錄下文件打包
const JSZip = require('jszip'); const path = require('path'); const RawSource = require('webpack-sources').RawSource; const zip = new JSZip(); module.exports = class ZipPlugin { constructor(options) { this.options = options; } apply(compiler) { compiler.hooks.emit.tapAsync('ZipPlugin', (compilation, callback) => { const folder = zip.folder(this.options.filename); for (let filename in compilation.assets) { const source = compilation.assets[filename].source(); folder.file(filename, source); } zip.generateAsync({ type: 'nodebuffer' }).then((content) => { const outputPath = path.join( compilation.options.output.path, this.options.filename + '.zip' ); const outputRelativePath = path.relative( compilation.options.output.path, outputPath ); compilation.assets[outputRelativePath] = new RawSource(content); callback(); }); }); } }
webpack 中許多對象擴展自 Tapable
類。這個類暴露 tap
, tapAsync
和 tapPromise
方法,能夠使用這些方法,注入自定義的構建步驟,這些步驟將在整個編譯過程當中不一樣時機觸發。
分別 對應是不是異步仍是同步鉤子;同步的鉤子只能用tap;異步鉤子須要callback() 我的以爲能夠理解成next()
compiler鉤子詳細文檔介紹(https://webpack.docschina.org/api/compiler-hooks/)
5⃣️ 在輸出文件時,動態修改部分文件的內容
class MyPlugin { constructor(options){ this.options = options } apply(compiler){ const {name} = this.options; compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => { for (let filename in compilation.assets) { console.log(filename) if(/\.html$/.test(filename)){ let source = compilation.assets[filename].source(); source = source.replace(/(\/js)|(\/css)/g, '.$1$2'); //修改連接路徑 compilation.assets[filename] = new RawSource(source) } } callback(); }) } }