玩轉webpack筆記


1.自動清理構建目錄產物

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前綴

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 自動轉化成rem

px2rem-loader

.page {
  font-size: 12px; /*no*/
  width: 375px; /*no*/
  height: 40px;
}

后面有 /*no*/這種注釋語法會不進行 rem 的轉換

目前官方推薦我們使用vw、vh進行適配,postcss-px-to-viewport

4. 靜態資源內聯(代碼層面、請求層面)

代碼層面: 初始化腳本;上報相關打點;css內聯避免頁面閃動;

請求層面:減少http請求數(url-loader)

raw-loader  內聯html,js腳本

index.html

${ require('raw-loader!./meta.html')} // html-webpack-plugin 默認使用ejs引擎的語法,可以用${}; 內聯loader也是從右到左執行

css內聯

1⃣️借助style-loader

2⃣️html-inline-css-webpack-plugin

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 類。這個類暴露 taptapAsync 和 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();
       })
   }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM