【webpack】webpack優(yōu)化1

導(dǎo)航
前置知識
wepback打包優(yōu)化 ( 注意第4點(diǎn)splitChunks和dllPlugin的區(qū)別 )
手寫一個loader

(1) 前置知識

一. 從零開始配置webpack可能會用到的依賴

- webpack webpack-cli

- html-weback-plugin
  - 優(yōu)化項(xiàng):minify:{ collapseWhitespace, removeAttributeQuotes} 折疊成一行,刪除html屬性的引號
  - 注意:優(yōu)化項(xiàng)minify主要用于mode: 'production'因?yàn)閴嚎s會增加打包時間
  - collapse 是坍塌的意思
  - hash: true // html文件引入資源時,在資源后面添加hash串
  - template
  - filename

- webpack-dev-server
  - contentBase // 啟動服務(wù)的文件夾
  - open: true //打開瀏覽器
  - port
  - compress: true // 開啟gzip壓縮
  - progress: true //打包進(jìn)程
  - hot: true //開啟熱更新,主要用于 webpack.HotModuleReplacementPlugin()
  - proxy // 設(shè)置代理
  - before // 鉤子函數(shù),用來模擬數(shù)據(jù)
  - webpack-dev-server除了在配置文件中配置,還可以寫成命令行,如下
  - webpack-dev-server --open --compress --progress --color

- css相關(guān)
  - style-loader
  - css-loader
  - less-loader
  - 抽離css
  - mini-css-extract-plugin 抽離css
    - MiniCssExtractPlugin.loader代替style-loader,因?yàn)椴挥胹tyle方式插入,而是抽離單獨(dú)文件
    - 在plugins中 new MiniCssExtractPlugin({ filename, chunkFilname, ignoreOrder })
  - 前綴
  - postcss-loader 解決css前綴,需要另外配置postcss.config.js,注意順序,放在css-loader下面,先加載
    - 單獨(dú)配置 postcss.config.js
    - 需要配合 autoprefixer
    - 注意:autoprefixer需要給出瀏覽器的一些信息,所以要在 package.json 中添加 browserslist
  - autoprefixer 解決css前綴,還需要package.json中配置 browserslist
    - "browserslist": [ // 在package.json中添加
        "defaults",
        "not ie < 9",
        "last 3 version",
        ">1%",
        "iOS 7",
        "last 3 iOS versions"
       ]
  - 壓縮css 
  - (注意用于生產(chǎn)環(huán)境才去壓縮css,因?yàn)橛绊懘虬俣?
  - optimize-css-assets-webapck-plugin 優(yōu)化css,比如壓縮,壓縮完后,production環(huán)境還需使用uglifyjs壓縮js
  - uglifyjs-webpack-plugin 壓縮js
  - optimization: { // 需要把 mode: 'production’才能看到壓縮css和js的效果,和上面的html的優(yōu)化一樣
      minimizer: [
        new OptimizeCssAssetsWebpackPlugin({}),
        new UglifyjsWebpackPlugin({
          cache: true,
          parallel: true, // 平行,并行的意思
          sourceMap: true, // 調(diào)試映射
        })
      ]
    }
  - css: 如何把所要打包的css抽離到css/目錄下 => MiniCssExtractPlugin => filename => 'css/[name].css'
  - https://webpack.js.org/plugins/mini-css-extract-plugin/#root


- js相關(guān)
  - babel-loader
  - @babel/core
  - @babel/preset-env
  - @babel/plugin-proposal-decorators 注意順序和 legacy 配置項(xiàng) // legacy: 是遺產(chǎn)的意思
  - @babel/plugin-proposal-class-properties 注意loose配置項(xiàng)
  - @babel/plugin-transform-runtime
  - @babel/runtime 注意是 dependencies 而不是 devDependencies
  - @babel/polyfill 注意是 dependencies 不是 devDependencies,還需在入口js文件引入,才能解析更高級的js語法,如includes
  - .babelrc
  - 
{
  "presets": [
    ["@babel/preset-env", {
      "modules": false // 關(guān)閉babel的模塊轉(zhuǎn)換功能,保留 es6 模塊化語法
    }]
  ],
  "plugins": [
    ["@babel/plugin-proposal-decorators", {"legacy": true}], // 注意順序,需要寫在class插件的前面,legacy不能少
    ["@babel/plugin-proposal-class-properties", {"loose": true}], // proposal:是提案的意思
    ["@babel/plugin-transform-runtime"], // 配置項(xiàng)都可以寫成數(shù)組形式,后面還可根一個配置對象
    ["@babel/plugin-syntax-dynamic-import"] // 用于來加載模塊,語法動態(tài)加載插件
  ]
}


- eslint eslint-loader babel-eslint
  - 需要單獨(dú)配置  .eslintrc.json 文件,配置rules
  - eslint-loader如果需要保證加載的順序最先執(zhí)行,要設(shè)置配置項(xiàng): enforce: 'pre'
  - babel-eslint // 需要安裝bebel-eslint插件 Cannot find module 'babel-eslint'
{
    "parser": "babel-eslint", // 這里必須設(shè)置
    "parserOptions": {
        "sourceType": "module",
        "allowImportExportEverywhere": true
    },
    "rules": {
        "indent": "off"
    },
    "env": {}
}


- loader的類型
  - pre     前置loader
  - post    后置loader
  - normal  普通loader
  - inline  內(nèi)聯(lián)loader


- expose-loader
  - expose-loader用來暴露全局對象,即可以通過window.屬性的方式訪問
  - expose: 是暴露的意思
  - 書寫方式有三種
  - (1)
  - 直接在入口js中引入,require('expose-loader?$!jquery') // 把jquery用變量 $ 暴露給全局
  - (2)
  - 在webpack.config.js的module中配置expose-loader
  -   {
        test: require.resolve('jquery'), // require.resolve()是nodejs中的函數(shù)
        use: [
          {
            loader: 'expose-loader',
            options: 'jquery' // 暴露成window.jquery
          },
          {
            loader: 'expose-loader',
            options: '$'  // 暴露成window.$
          }
        ]
      },
    - (3)
    - 在每個模塊中注入$,不需要在每個模塊中再引入,可以使用 webpack.ProvidePlugin插件,再plugins中加入
    - new webpack.ProvidePlugin({
        $: 'jquery'
      }) 


- 圖片相關(guān)
  - file-loader
  - url-loader 可以設(shè)置大小限制,小于時用url-loader轉(zhuǎn)成base64,大于時使用file-loader加載圖片
  - html-withimg-loader 在html中通過路徑加載圖片,在打包后能不受影響

  - 圖片:如何把所有要打包的圖片都抽離到 img/目錄下 => url-loader => options => outputPath
  - css: 如何把所要打包的css抽離到css/目錄下 => MiniCssExtractPlugin => filename => 'css/[name].css'
  - 注意:當(dāng)圖片是babes64時即在limit范圍內(nèi)時,使用 outputPath 輸出到指定文件夾無效,因?yàn)闆]有靜態(tài)圖片資源而是base64



- 公共路徑
  - 在output中設(shè)置 publicPath 即所有引用都會加上公共路徑前綴
  - 如果只是想給圖片添加公共路徑的話: url-loader => options => publicPath:- 打包7多頁面應(yīng)用
  - entry對象中使用不同的key值
  - output中 => filename: '[name].[hash:8].js'
  - html則要多次 new HtmlWebpckPlugin()
  - 但是上面生成多個html會出現(xiàn)每個html都應(yīng)用所有的js,而不是只引用相對應(yīng)的js => 通過chunks: 

- source-map源碼映射
  - devtool: 'source-map' // 顯示行數(shù),產(chǎn)生map文件
  - devtool: 'eval-source-amp' // 顯示函數(shù),不產(chǎn)生map文件

- 時時打包
  - watch: true
  - watchOptions: { 
      aggregateTimeout: 300, // 防抖
      poll: 1000, // 每秒詢問1000次
      ignored: /node_modules/
    }
  - aggregate: 總計,合計的意思
  - poll: 投票數(shù)的意思
  - https://www.cnblogs.com/chris-oil/p/8856020.html

- clean-wepack-plugin // https://github.com/johnagan/clean-webpack-plugin
- copy-webpack-plugin // https://github.com/webpack-contrib/copy-webpack-plugin
- BannerPlugin 是 webpack自帶的plugin,用于在js文件開頭注釋一些說明內(nèi)容
  - new webpack.BannerPlugin({ banner: ' by woow.wu'})

- wepack設(shè)置代理
  - devServer.proxy
  - proxy: {
    '/api': {
      target: 'http://localhost:6000',
      pathRewrite: {
        '/api': ''   // 將/api重寫成空字符串,即雖然前端請求時加了/api,但是真正代理到后端時,是去掉了/api的
      }
    }
  }

- wepback中moke數(shù)據(jù)
- devServer.before  // https://webpack.js.org/configuration/dev-server/#devserverproxy
- 這樣起始就不用在自己寫server了,并且也不用啟動node服務(wù),很方便
-   devServer: {
      before: function(app, server) {
        app.get('/some/path', function(req, res) {
          res.json({ custom: 'response' });
        });
      }    
    }

- resolve
  - resolve.alias // 配置別名
  - resolve.extensions // 引入資源時,省略后綴的加載順序
  - alias: { 
      component: path.resolve(__dirname, 'src/component/')
    },
  - extensions: ['.js', '.less', '.css'] 

- 環(huán)境變量
  - wepack.DefinePlugin // https://webpack.js.org/plugins/define-plugin/#root
  -   new webpack.DefinePlugin({
      DEV: JSON.stringify('dev'),
      FEATURE: JSON.stringify(true)
    })

- webpack-merge 在base的基礎(chǔ)上定制化config文件
- module.exports = merge(base, devConfig)
- // https://webpack.js.org/guides/production/#setup

- happypack 多線程打包
- // https://github.com/amireh/happypack
- {
  test: /\.js$/,
  use: 'happypack/loader?id=js',
  },
  new HappyPack({
      id: 'js',
      use: [{
        loader: 'babel-loader'
      }]
    }),

- tree-shaking
- tree-shaking能在打包的時候,自動去除沒用的代碼
- 比如一個sum,sub函數(shù),在import后只用到了sum,則sub函數(shù)在生產(chǎn)環(huán)境mode:production打包會被去除
- shaking: 是搖晃的意思
- 注意:
  - 只有 import 和 export 語法是支持 tree-shaking的
  - require和modue.exports并不支持 tree-shaking
- 注意:
  - 當(dāng)使用了 optimize-css-assets-webpack-plugin后,tree-shaking會不生效,則需要下面的插件 terser-webpack-plugin
  - terser-webpack-plugin 
  - terser: 是簡要的意思
  -  // https://segmentfault.com/q/1010000014940767

- 抽離公共組件和第三方組件,可以緩存,則不需要重復(fù)加載
- optimization => splitChunks => cacheGroups => vendors和commons
- priority: 是優(yōu)先的意思
- optimization: {
    minimizer: {}, // 壓縮css和js的配置項(xiàng)
    splitChunks: {
      cacheGroups: {
        commons: {
          name: 'commons',
          chunks: 'initial',
          minChunks: 1,
          priority: 10,
          minSize: 0,
        },
        vendors: { // vendor是小販的意思
          test: /node_modules/, // 范圍是node_modules中的第三方依賴,注意zhe
          name: 'vendors', // 抽離出來的包的名字
          chunks: 'initial', // 初始化加載的時候就抽離公共代碼
          minChunks: 1, // 被引用的次數(shù)
          priority: 11, // priority: 是優(yōu)先級的意思,數(shù)字越大表示優(yōu)先級越高
          minSize: 0,
        }
      }
    }
  }


- 熱更新
- new webpack.HotModuleReplacementPlugin() // 熱更新
- new webpack.NameModulesPlugin() // 打印熱更新模塊的路徑
- 1. 首先在 devServer 配置中增加 hot: true,表示開啟熱更新
- 2. 在plugins數(shù)組中 new new webpack.HotModuleReplacementPlugin()
- 3. 在入口js文件中:
- if (module.hot) { // 如果開啟了熱更新
  module.hot.accept('./component/index.js', function () { // 路徑中的js文件改變,則執(zhí)行回調(diào)函數(shù)
    const res = require('./component/index.js')
    console.log(res.default.a, '模塊熱更新, 最新的a值')
  })
}



- 懶加載
- @babel/plugin-syntax-dynamic-import // 語法動態(tài)引入插件
- syntax: 是語法的意思
- dynamic:是動態(tài)的意思
- import只能用在頂部報錯:解決需要安裝 babel-eslint 插件
- 安裝,babel-eslint插件,并且在 .eslintrc.json中做如下配置
- {
    "parser": "babel-eslint",
    "parserOptions": {
        "sourceType": "module",
        "allowImportExportEverywhere": true
    },
    "rules": {
        "indent": "off"
    },
    "env": {}
}
- https://stackoverflow.com/questions/39158552/ignore-eslint-error-import-and-export-may-only-appear-at-the-top-level
- 具體使用懶加載如下:
- // 懶加載
const button = document.createElement('button')
button.addEventListener('click', () => {
  // console.log('button clicked')
  import('./component/index.js').then(res => {
    console.log(res.default.a)
  })
})
button.innerHTML = 'button'
document.body.appendChild(button)
- 本質(zhì)上import().then()使用 jsonp實(shí)現(xiàn)的

wepback 打包優(yōu)化

1. exclude 和 include
  - 使用 babel-loader 解析js文件時
  - 如果在js中文件中,引用了第三方模塊,這個時候,時不需要再用 babel-loader 進(jìn)行編譯的,因?yàn)橐呀?jīng)打包過了的
  - 所以添加 exclude: /node_modules/ 配置項(xiàng),排除node_modules文件夾  
  - 注意:include,exclude沒必要用于 (圖片) 相關(guān)的loader
  - 原理:降低loader的使用頻率,從而提高webpack的打包速度
    {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
          }
        ]
      }



2. 在dev環(huán)境中,沒有必要使用 optimize-css-assets-webpack-plugin , mini-css-extract-plugin
  - 盡量減少 plugin 的使用,選擇性能比較好的,官方推薦的plugin



3. resolve
  - extensions
    - resolve中的 extensions 中一般只對 js或者jsx這樣的邏輯型文件時,使用 extensions
    - 因?yàn)槿绻押芏嗪缶Y都配置在這里,就會調(diào)用多次底層去判斷是否命中,消耗性能
    - extensions: ['.js', '.jsx']
    - 在引入 import A from 'component/A' 這里可以省略擴(kuò)展名 .js 或者 .jsx
  - mainFiles
    - mainFiles: ['index']
    - 如果配置了index,則在引入的時候可以這樣寫 import a from './src/component/'
resolve: {
  modules: ['node_modules'],
  alias: {
    component: path.resolve(__dirname, 'src/component/')
  },
  extensions: ['.js', '.jsx'] // extensions是擴(kuò)展的意思,擴(kuò)展名
  mainFiles: ['index'] // 先找以index開頭的文件
}



4. DllPluguin和 DllReferencePlugin 提高打包速度 // 動態(tài)鏈接庫
  - add-asset-html-webpack-plugin // 一個在html引入靜態(tài)文件的插件
    - filepath: 需要引入的文件路徑
  - webpack.DllPlugin() // 主要是用于生成映射文件
    - name: '[name]' // 必須和output中的library相同,表示暴露到全局的庫的名稱
    - path: path.resolve(__dirname, 'dll', '[name].manifest.json') // .manifest.json就是映射文件
  - webpack.DllReferencePlugin() // 最新引入的是映射文件中的依賴,而不是在node_modules中去查找
    - manifest: path.resolve(__dirname, 'dll', 'vendors.manifest.json')
  - 原理:
    - 使用splitChunks可以單獨(dú)抽離出第三方包,但是每次打包都會執(zhí)行抽離的過程
    - 所以需要抽離并且第一次打包后,第二次打包就不需要再打包抽離的包了
    - 所以要經(jīng)過兩步:抽離 和 只打包一次
  - 具體步驟:
    - 1. 新建webpack.dll.js // 生成兩類文件,一個是js文件,一個是manifest.json映射文件
    - 2. 在html中引入抽離出來的js文件 // 這里可以使用插件,add-asset-webpack-plugin
    - 3. 在webpack.common.js中引用manifest.json文件
  - 調(diào)試
   - 在webpack.common.js中注釋掉 new wepback.DllReferencePlugin前后對比打包時間
  - 代碼
webpack.dll.js
module.exports = {
  mode: 'development',
  entry: {
    react: ['react', 'react-dom']'lodash', 'moment']
  },
  output: {
    filename: '[name].dll.js',
    path: path.resolve(__dirname, 'dll'),
    library: '[name]', // 暴露到全局的變量名
    libraryTarget: 'var' // 以var的方式暴露
  },
  plugins: [
    new webpack.DllPlugin({
      name: '[name]', // name === library 必須一樣 (在manifest.json中對應(yīng)的各個包的名字)
      path: path.resolve(__dirname, 'dll', '[name].manifest.json') // 生成在dll文件夾下的name.manifest.json文件
    })
  ]
}
webpack.common.js
new AddAssetHtmlWebpackPlugin({
    filepath: path.resolve(__dirname, 'dll', '*.dll.js')
}),
new wepback.DllReferencePlugin({
    manifest: path.resolve(__dirname, 'dll', 'vendors.manifest.json')
})
// https://webpack.js.org/plugins/dll-plugin/#root
  - 優(yōu)化
    - 當(dāng)webpack.dll.js中的entry每個第三方包都單獨(dú)生成 .dll.js和 .manifest.json文件后,需要多次使用上面兩個插件
    - 所有可以用 nodejs 的 fs.readdirSync(path)去獲取dll文件夾的所有文件數(shù)組
    - 代碼如下:
const dllPlugins = []
fs.readdirSync(path.resolve(__dirname, 'dll')).forEach(item => {
    console.log(item, 'item')
    if (/\.dll\.js/.test(item)) {
        dllPlugins.push(
            new AddAssetHtmlWebpackPlugin({
                filepath: path.resolve(__dirname, 'dll', item)
            })
        )
    }
    if (/\.manifest\.json/.test(item)) {
        dllPlugins.push(
            new wepback.DllReferencePlugin({
                manifest: path.resolve(__dirname, 'dll', item)
            })
        )
    }
})
plugins: [
...dllPlugins
]



5. happypack多線程打包
6. sourcmap調(diào)試映射文件的各種類型
7. 結(jié)合stats分析打包結(jié)果

手寫一個loader

  • loader-utils
  • this.query
  • this.callback
  • this.async
  • resolveLoader // webpack配置對象,moudles屬性
前置知識:
1. loader函數(shù)中的第一個參數(shù)表示源代碼
2. 不能寫成箭頭函數(shù),因?yàn)樾枰ㄟ^ this 獲取很多api
3. 如何獲取loader中的配置參數(shù) options對象
  - this.query // 指向的就是 options對象
    // 如果 loader 中沒有 options,而是以 query 字符串作為參數(shù)調(diào)用時,this.query 就是一個以 ? 開頭的字符串
  - 注意:
  - this.query已經(jīng)廢棄,使用 loader-utils 中的 getOptions 來獲取 options對象

4. loader-utils
  - 通過loader-utils中的 getOptions 獲取 loader的options配置對象
5. this.callback
  - 參數(shù)
  - 第一個參數(shù):err // Error 或者 null
  - 第二個參數(shù):content // string或者buffer
  - 第三個參數(shù):sourceMap // 可選,必須是一個可以被這個模塊解析的 source map
  - 第四個參數(shù):meta //可選
  - // https://www.webpackjs.com/api/loaders/#this-callback
this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap,
  meta?: any
);

6. this.async // 處理loader中有異步操作
  - this.async()方法返回 this.callback
7. resolveLoader // webpack配置項(xiàng)
- modules: ['node_modules', './src/loaders']
 // 在尋找loader的時候,先去node_modules文件夾中共尋找,沒找到再去'./src/loaders'文件夾中找




------------
代碼:replace-loader.js // 如下
------------
const loaderUtils = require('loader-utils')

module.exports = function (source) {
  console.log(this.query, 'this.query')

  const options = loaderUtils.getOptions(this) // 獲取laoder的options對象
  console.log(options, 'loader-utils中的getOtions')

  const callback = this.async(); // this.async()方法返回的是 this.callback

  setTimeout(() => { // 注意這里的setTimeout的環(huán)境
    const result = source.replace('guoqing', options.replacename) // 拿到options對對象中設(shè)置的replacename屬性
    callback(null, result)
  }, 1000);

  // return source.replace('guoqing', this.query.name)
  // return source.replace('guoqing', options.name)
}


------------
代碼:webpack.dev.js // 如下
------------
resolveLoader: {
  modules: ['node_modules', './src/loaders'] // 先找node_module再找后面的文件夾
},
module: {
  rules: [
    {
      test: /\.js$/,
      use: [
        {
          loader: 'replace-loader.js', // 配置了resolveLoader的modules后,這里就直接寫了
          options: {
            replacename: '2019 new new good'
          }
        }
      ]
    }
  ]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容