webpack構(gòu)建

webpack.config.js

參考鏈接:2020年了,再不會(huì)webpack敲得代碼就不香了(近萬(wàn)字實(shí)戰(zhàn))

const path = require('path')
#每次執(zhí)行npm run build會(huì)發(fā)現(xiàn)dist文件夾里會(huì)殘留上次打包的文件,這里我們推薦一個(gè)plugin來(lái)幫我們?cè)诖虬敵銮扒蹇瘴募Aclean-webpack-plugin
const {CleanWebpackPlugin} = require('clean-webpack-plugin')

#html-webpack-plugin---webpack打包生成的js文件會(huì)被自動(dòng)引入html文件中
#生成多個(gè)html-webpack-plugin實(shí)例來(lái)解決多入口文件開(kāi)發(fā)的問(wèn)題
const HtmlWebpackPlugin = require('html-webpack-plugin')

#vue-loader 用于解析.vue文件
#vue-template-compiler 用于編譯模板
const vueLoaderPlugin = require('vue-loader/lib/plugin')

#webpack4.0以后,官方推薦使用mini-css-extract-plugin插件來(lái)打包c(diǎn)ss文件
const MiniCssExtractPlugin = require("mini-css-extract-plugin")

#process 對(duì)象是一個(gè) global (全局變量),提供有關(guān)信息,控制當(dāng)前 Node.js 進(jìn)程。作為一個(gè)對(duì)象,它對(duì)于 Node.js 應(yīng)用程序始終是可用的,故無(wú)需使用 require()。
#process是node全局屬性,直接用就行
#process.env屬性返回一個(gè)包含用戶環(huán)境信息的對(duì)象。#常用process.env.NODE_ENV來(lái)設(shè)置環(huán)境變量。在不同環(huán)境的機(jī)器上設(shè)置不同的 NODE_ENV,當(dāng)然這個(gè)字段也不一定。你也可以換成其他的NODE_ENV_NIZUISHUAI等等,反正是自定義的。參考鏈接:[Node環(huán)境變量 process.env 的那些事兒](https://segmentfault.com/a/1190000011683741)
#process.argv 屬性會(huì)返回一個(gè)數(shù)組,其中包含當(dāng) Node.js 進(jìn)程被啟動(dòng)時(shí)傳入的命令行參數(shù)。 第一個(gè)元素是 process.execPath。 如果需要訪問(wèn) argv[0] 的原始值,則參見(jiàn) process.argv0。 第二個(gè)元素是正被執(zhí)行的 JavaScript 文件的路徑。 其余的元素是任何額外的命令行參數(shù)。
#process.execPath 屬性返回啟動(dòng) Node.js 進(jìn)程的可執(zhí)行文件的絕對(duì)路徑名。比如'/usr/local/bin/node'
const devMode = process.argv.indexOf('--mode=production') === -1;#判斷當(dāng)前是否是dev環(huán)境

module.exports = {
  #entry: ["@babel/polyfill",path.resolve(__dirname,'../src/index.js')],    // 入口文件
  entry:{
    main:path.resolve(__dirname,'../src/main.js') 
  },
  output:{
    path:path.resolve(__dirname,'../dist'),#打包后的目錄
    filename:'js/[name].[hash:8].js',#打包后的文件名稱
    chunkFilename:'js/[name].[hash:8].js'
  },
  module:{
    rules:[
      {
       #用babel轉(zhuǎn)義js文件 # npm i -D babel-loader @babel/preset-env @babel/core
        test:/\.js$/,
        use:{
          #babel-loader只會(huì)將ES6/7/8語(yǔ)法轉(zhuǎn)換為ES5語(yǔ)法,但是對(duì)新api并不會(huì)轉(zhuǎn)換 例如(promise、Generator、Set、Maps、Proxy等)此時(shí)我們需要借助babel-polyfill來(lái)幫助我們轉(zhuǎn)換(用法可看entry配置示例)
          loader:'babel-loader',#當(dāng)寫法為babel-loader?cacheDirectory=true時(shí),表示可以將Babel編譯過(guò)的文件緩存起來(lái),下次只需要編譯更改過(guò)的代碼文件即可,可以大幅度加快打包時(shí)間
          options:{
            presets:['@babel/preset-env',{ modules: false }]#因?yàn)锽abel的預(yù)案(preset)默認(rèn)會(huì)將任何模塊類型都轉(zhuǎn)譯成CommonJS類型,這樣會(huì)導(dǎo)致tree-shaking失效。修正這個(gè)問(wèn)題也很簡(jiǎn)單,在.babelrc文件或在webpack.config.js文件中設(shè)置modules: false就好了
          }
        },
        exclude:/node_modules/#配置include exclude也可以減少webpack loader的搜索轉(zhuǎn)換時(shí)間。
      },
      {
        test:/\.vue$/,
        use:[{
          loader:'vue-loader',
          options:{
            compilerOptions:{
              preserveWhitespace:false
            }
          }
        }]
      },
      {
        test:/\.css$/,
        use:[{
          loader: devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
          options:{
            publicPath:"../dist/css/",
            hmr:devMode
          }
        },'css-loader',{
          loader:'postcss-loader',
          options:{
            plugins:[require('autoprefixer')]
          }
        }]#遵循從右向左解析原則
      },
      {
        test:/\.less$/,
        use:[{
          loader:devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
          options:{
            publicPath:"../dist/css/",
            hmr:devMode
          }
        },'css-loader','less-loader',{
          #為css添加瀏覽器前綴
          loader:'postcss-loader',
          options:{
            plugins:[require('autoprefixer')]
          }
        }]
      },
      #打包 圖片、字體、媒體、等文件
      #file-loader就是將文件在進(jìn)行一些處理后(主要是處理文件名和路徑、解析文件url),并將文件移動(dòng)到輸出的目錄中
      #url-loader 一般與file-loader搭配使用,功能與 file-loader 類似,如果文件小于限制的大小。則會(huì)返回 base64 編碼,否則使用 file-loader 將文件移動(dòng)到輸出的目錄中
      {
        test:/\.(jep?g|png|gif)$/,
        use:{
          loader:'url-loader',
          options:{
            limit:10240,
            fallback:{
              loader:'file-loader',
              options:{
                name:'img/[name].[hash:8].[ext]'
              }
            }
          }
        }
      },
      {
        test:/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        use:{
          loader:'url-loader',
          options:{
            limit:10240,
            fallback:{
              loader:'file-loader',
              options:{
                name:'media/[name].[hash:8].[ext]'
              }
            }
          }
        }
      },
      {
        test:/\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
        use:{
          loader:'url-loader',
          options:{
            limit:10240,
            fallback:{
              loader:'file-loader',
              options:{
                name:'media/[name].[hash:8].[ext]'
              }
            }
          }
        }
      }
    ]
  },
  #關(guān)于resolve的具體用法,參考鏈接:[webpack學(xué)習(xí)筆記--配置resolve](https://www.cnblogs.com/joyco773/p/9049760.html)
  resolve:{
    #當(dāng)我們代碼中出現(xiàn) import 'vue'時(shí), webpack會(huì)采用向上遞歸搜索的方式去node_modules 目錄下找。為了減少搜索范圍我們可以直接告訴webpack去哪個(gè)路徑下查找。也就是別名(alias)的配置。
    alias:{
      'vue$':'vue/dist/vue.runtime.esm.js',
      ' @':path.resolve(__dirname,'../src')
    },
    #webpack會(huì)根據(jù)extensions定義的后綴查找文件(頻率較高的文件類型優(yōu)先寫在前面)
    extensions:['*','.js','.json','.vue']
  },
  plugins:[
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template:path.resolve(__dirname,'../public/index.html')#打包后生成的js文件會(huì)被自動(dòng)引入html文件中
    }),
    new vueLoaderPlugin(),
    new MiniCssExtractPlugin({
      filename: devMode ? '[name].css' : '[name].[hash].css',
      chunkFilename: devMode ? '[id].css' : '[id].[hash].css'
    })
  ]
}

webpack.dev.js(開(kāi)發(fā)環(huán)境配置文件)

mode可設(shè)置development production兩個(gè)參數(shù)
如果沒(méi)有設(shè)置,webpack4 會(huì)將 mode 的默認(rèn)值設(shè)置為 production。
production模式下會(huì)進(jìn)行tree shaking(去除無(wú)用代碼)和uglifyjs(代碼壓縮混淆)

const Webpack = require('webpack')
const webpackConfig = require('./webpack.config.js')
const WebpackMerge = require('webpack-merge')
module.exports = WebpackMerge(webpackConfig,{
  mode:'development',
  devtool:'cheap-module-eval-source-map',
  
  #配置webpack-dev-server進(jìn)行熱更新  npm i -D webpack-dev-server
  devServer:{
    port:3000,
    hot:true,
    contentBase:'../dist'
  },
  plugins:[
    new Webpack.HotModuleReplacementPlugin()
  ]
})

webpack.prod.js(生產(chǎn)環(huán)境配置文件)

#生產(chǎn)環(huán)境主要實(shí)現(xiàn)的是壓縮代碼、提取css文件、合理的sourceMap、分割代碼
#需要安裝以下模塊:
#npm i -D  webpack-merge copy-webpack-plugin optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin

const path = require('path')
const webpackConfig = require('./webpack.config.js')
const WebpackMerge = require('webpack-merge')#合并配置
const CopyWebpackPlugin = require('copy-webpack-plugin')#拷貝靜態(tài)資源
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')#壓縮css

#webpack mode設(shè)置production的時(shí)候會(huì)自動(dòng)壓縮js代碼。原則上不需要引入uglifyjs-webpack-plugin進(jìn)行重復(fù)工作。但是optimize-css-assets-webpack-plugin壓縮css的同時(shí)會(huì)破壞原有的js壓縮,所以這里我們引入uglifyjs進(jìn)行壓縮
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')#壓縮js

module.exports = WebpackMerge(webpackConfig,{
  mode:'production',
  devtool:'cheap-module-source-map',
  plugins:[
    new CopyWebpackPlugin([{
      from:path.resolve(__dirname,'../public'),
      to:path.resolve(__dirname,'../dist')
    }]),
  ],
  optimization:{
    minimizer:[
      #或者使用webpack-parallel-uglify-plugin 增強(qiáng)代碼壓縮
      new UglifyJsPlugin({//壓縮js
        cache:true,
        parallel:true,
        sourceMap:true
    }),
    new OptimizeCssAssetsPlugin({})
    ],
    splitChunks:{
      chunks:'all',
      cacheGroups:{
        libs: {
          name: "chunk-libs",
          test: /[\\/]node_modules[\\/]/,
          priority: 10,
          chunks: "initial" // 只打包初始時(shí)依賴的第三方
        }
      }
    }
  }
})

優(yōu)化webpack配置

1.優(yōu)化打包速度(構(gòu)建速度指的是我們每次修改代碼后熱更新的速度以及發(fā)布前打包文件的速度。)

  • 合理的配置mode參數(shù)與devtool參數(shù)
  • 縮小文件的搜索范圍(配置include exclude alias noParse extensions)
  • 使用HappyPack開(kāi)啟多進(jìn)程Loader轉(zhuǎn)換npm i -D happypack
    • 受限于Node是單線程運(yùn)行的,所以Webpack在打包的過(guò)程中也是單線程的,特別是在執(zhí)行Loader的時(shí)候,長(zhǎng)時(shí)間編譯的任務(wù)很多,這樣就會(huì)導(dǎo)致等待的情況
    • HappyPack可以將Loader的同步執(zhí)行轉(zhuǎn)換為并行的,這樣就能充分利用系統(tǒng)資源來(lái)加快打包效率了
     module:{
         loaders:[{
             test:/\.js$/,
             include:[resolve('src')],
             exclude:/node_modules/,
             loader:'happypack/loader?id=happybabel'
         }]
     },
     plugins:[
     new HappyPack({
         id:'happybabel',
         loaders:['babel-loader?cacheDirectory'],
         threads:4#開(kāi)啟4個(gè)線程
     })
     ]
    
  • 使用webpack-parallel-uglify-plugin 增強(qiáng)代碼壓縮npm i -D webpack-parallel-uglify-plugin
  • 抽離第三方模塊(使用webpack內(nèi)置的DllPlugin DllReferencePlugin進(jìn)行抽離)
#對(duì)于開(kāi)發(fā)項(xiàng)目中不經(jīng)常會(huì)變更的靜態(tài)依賴文件。類似于我們的elementUi、vue全家桶等等。因?yàn)楹苌贂?huì)變更,所以我們不希望這些依賴要被集成到每一次的構(gòu)建邏輯中去。
#這樣做的好處是每次更改我本地代碼的文件的時(shí)候,webpack只需要打包我項(xiàng)目本身的文件代碼,而不會(huì)再去編譯第三方庫(kù)。以后只要我們不升級(jí)第三方包的時(shí)候,那么webpack就不會(huì)對(duì)這些庫(kù)去打包,這樣可以快速的提高打包的速度。
  • 配置緩存
#我們每次執(zhí)行構(gòu)建都會(huì)把所有的文件都重復(fù)編譯一遍,這樣的重復(fù)工作是否可以被緩存下來(lái)呢,答案是可以的,目前大部分 loader 都提供了cache 配置項(xiàng)。
#比如在 babel-loader 中,可以通過(guò)設(shè)置cacheDirectory 來(lái)開(kāi)啟緩存,babel-loader?cacheDirectory=true 就會(huì)將每次的編譯結(jié)果寫進(jìn)硬盤文件(默認(rèn)是在項(xiàng)目根目錄下的node_modules/.cache/babel-loader目錄內(nèi),當(dāng)然你也可以自定義)

#但如果 loader 不支持緩存呢?我們也有方法,我們可以通過(guò)cache-loader ,它所做的事情很簡(jiǎn)單,就是 babel-loader 開(kāi)啟 cache 后做的事情,將 loader
#的編譯結(jié)果寫入硬盤緩存。再次構(gòu)建會(huì)先比較一下,如果文件較之前的沒(méi)有發(fā)生變化則會(huì)直接使用緩存。使用方法如官方 demo 所示,在一些性能開(kāi)銷較大的 loader 之前添加此 loader即可。
  • 如果你確定一個(gè)文件下沒(méi)有其他依賴,就可以使用module.noParse屬性讓W(xué)ebpack不掃描該文件,這種方式對(duì)于大型的類庫(kù)很有幫助
    2.優(yōu)化打包文件體積
  • 引入webpack-bundle-analyzer分析打包后的文件
  • externals
  • Tree-shaking(tree-shaking的主要作用是用來(lái)清除代碼中無(wú)用的部分。)
  • scope Hoisting:它會(huì)分析出模塊間的依賴關(guān)系,盡可能的把打包出來(lái)的模塊合并到一個(gè)函數(shù)中去
    module.exports={
        optimization:{
            concatenateModules:true
        }
    }
    

生產(chǎn)環(huán)境中刪除console.log

參考鏈接:vuecli3+webpack4優(yōu)化實(shí)踐(刪除console.log和配置dllPlugin)

#可以通過(guò)配置webpack4中的terser-webpack-plugin插件達(dá)成目的,compress參數(shù)配置如下:
module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
              warnings: false,
              drop_console: true,
              drop_debugger: true,
              pure_funcs: ['console.log']
          },
        },
      }),
    ],
  },
};

#在 vue.config.js 中的 configureWebpack 選項(xiàng)提供一個(gè)對(duì)象會(huì)被 webpack-merge 合并入最終的 webpack配置,因此vue-cli3構(gòu)建的項(xiàng)目中只需要修改terserOptions即可,vue.config.js配置如下:
module.exports = {
  publicPath: '/',
  outputDir: 'dist',
  devServer: {
    port: 8080,
    https: false,
    hotOnly: true,
    disableHostCheck: true,
    open: true,
  },
  productionSourceMap: false, // 生產(chǎn)打包時(shí)不輸出map文件,增加打包速度
  configureWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      config.optimization.minimizer[0].options.terserOptions.compress.warnings = false
      config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
      config.optimization.minimizer[0].options.terserOptions.compress.drop_debugger = true
      config.optimization.minimizer[0].options.terserOptions.compress.pure_funcs = ['console.log']
    }
  }
}

#配置完成后使用 vue inspect --mode=production > output.js 命令審查項(xiàng)目的 webpack 配置

webpack魔術(shù)注釋:

參考鏈接:項(xiàng)目不知道如何做性能優(yōu)化?不妨試一下代碼分割

  • 魔術(shù)注釋是由 Webpack 提供的,可以為代碼分割服務(wù)的一種技術(shù)。通過(guò)在 import 關(guān)鍵字后的括號(hào)中使用指定注釋,我們可以對(duì)代碼分割后的 chunk 有更多的控制權(quán)
import (
  /* webpackChunkName: “my-chunk-name” */
  /* webpackMode: lazy */#webpackMode 的默認(rèn)值為 lazy 它會(huì)使所有異步模塊都會(huì)被單獨(dú)抽離成單一的 chunk,若設(shè)置該值為 lazy-once,Webpack 就會(huì)將所有帶有標(biāo)記的異步加載模塊放在同一個(gè) chunk 中。
  './someModule'
)
#通過(guò)添加 webpackPrefetch 魔術(shù)注釋,Webpack 令我們可以使用與 <link rel=“prefetch”> 相同的特性。讓瀏覽器會(huì)在 Idle 狀態(tài)時(shí)預(yù)先幫我們加載所需的資源,善用這個(gè)技術(shù)可以使我們的應(yīng)用交互變得更加流暢。
import(
  /* webpackPrefetch: true */ 
  './someModule'
)

使用vue-cli時(shí),Vue.config.js 配置選項(xiàng)

參考鏈接:vue.config.js 配置

module.exports = {
  publicPath: './',#基本路徑
  outputDir: 'dist', #構(gòu)建時(shí)的輸出目錄
  assetsDir: 'static', #放置靜態(tài)資源的目錄
  indexPath: 'index.html', #html的輸出路徑#指定生成的 index.html 的輸出路徑 (相對(duì)于 outputDir)。也可以是一個(gè)絕對(duì)路徑。
  filenameHashing: true, #文件名哈希
  pages: {  #用于多頁(yè)配置,默認(rèn)是 undefined
    index: {
      entry: 'src/index/main.js', # page 的入口文件
      template: 'public/index.html', # 模板文件
      filename: 'index.html',  # 在 dist/index.html 的輸出文件
      title: 'Index Page', # 當(dāng)使用頁(yè)面 title 選項(xiàng)時(shí), # template 中的 title 標(biāo)簽需要是 <title><%= htmlWebpackPlugin.options.title %></title>
      chunks: ['chunk-vendors', 'chunk-common', 'index'] # 在這個(gè)頁(yè)面中包含的塊,默認(rèn)情況下會(huì)包含 # 提取出來(lái)的通用 chunk 和 vendor chunk。
    },
    subpage: 'src/subpage/main.js' # 當(dāng)使用只有入口的字符串格式時(shí), # 模板文件默認(rèn)是 `public/subpage.html` # 如果不存在,就回退到 `public/index.html`。 # 輸出文件默認(rèn)是 `subpage.html`。
  },
  lintOnSave: true,  #是否在保存的時(shí)候使用 `eslint-loader` 進(jìn)行檢查。
  runtimeCompiler: false,#是否使用帶有瀏覽器內(nèi)編譯器的完整構(gòu)建版本
  transpileDependencies: ['resize-detector'], #babel-loader 默認(rèn)會(huì)跳過(guò) node_modules 依賴。
  productionSourceMap: true,  #  是否為生產(chǎn)環(huán)境構(gòu)建生成 source map?
  crossorigin: '', #  設(shè)置生成的 HTML 中 <link rel="stylesheet"> 和 <script> 標(biāo)簽的 crossorigin 屬性。 
  integrity: false, # 在生成的 HTML 中的 <link rel="stylesheet"> 和 <script> 標(biāo)簽上啟用Subresource Integrity (SRI)
  
  configureWebpack: () => {}, #(Object | Function)#如果這個(gè)值是一個(gè)對(duì)象,則會(huì)通過(guò) webpack-merge 合并到最終的配置中#如果這個(gè)值是一個(gè)函數(shù),則會(huì)接收被解析的配置作為參數(shù)。該函數(shù)及可以修改配置并不返回任何東西,也可以返回一個(gè)被克隆或合并過(guò)的配置版本。
  chainWebpack: () => {}, #允許對(duì)內(nèi)部的 webpack 配置進(jìn)行更細(xì)粒度的修改。
  # 配置 webpack-dev-server 行為。
  
  devServer: {
    open: process.platform === 'darwin',
    host: '0.0.0.0',
    port: 8080,
    https: false,
    hotOnly: false, 
    # 查閱 https:#github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli/cli-service.md#配置代理
    proxy: {
      '/api': {
        target: 'http:#app.rmsdmedia.com',
        changeOrigin: true,
        secure: false,
        pathRewrite: {
          '^/api': ''
        }
      },
      '/foo': {
        target: '<other_url>'
      }
    }, 
    before: app => {}
  },
  
  # CSS 相關(guān)選項(xiàng)
  css: {
    # 將組件內(nèi)的 CSS 提取到一個(gè)單獨(dú)的 CSS 文件 (只用在生產(chǎn)環(huán)境中)
    # 也可以是一個(gè)傳遞給 `extract-text-webpack-plugin` 的選項(xiàng)對(duì)象
    extract: true,
    # 是否開(kāi)啟 CSS source map?
    sourceMap: false,
    # 為預(yù)處理器的 loader 傳遞自定義選項(xiàng)。比如傳遞給 # Css-loader 時(shí),使用 `{ Css: { ... } }`。
    loaderOptions: {
      css: {
        # 這里的選項(xiàng)會(huì)傳遞給 css-loader
      },
      postcss: {
        # 這里的選項(xiàng)會(huì)傳遞給 postcss-loader
      }
    },
    # 為所有的 CSS 及其預(yù)處理文件開(kāi)啟 CSS Modules。 # 這個(gè)選項(xiàng)不會(huì)影響 `#.vue` 文件。
    modules: false
  }, 
  # 在生產(chǎn)環(huán)境下為 Babel 和 TypeScript 使用 `thread-loader` # 在多核機(jī)器下會(huì)默認(rèn)開(kāi)啟。
  parallel: require('os').cpus().length > 1, 
  # PWA 插件的選項(xiàng)。 # 查閱 https:#github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-pwa/README.md
  pwa: {},
  # 三方插件的選項(xiàng)
  pluginOptions: {
    # ...
  }
}

.browserslistrc文件配置或者是package.json中配置的browserslist對(duì)象

具體配置參考鏈接:browserslist 目標(biāo)瀏覽器配置表

> 1% #全球超過(guò)1%人使用的瀏覽器
last 2 versions#所有瀏覽器兼容到最后兩個(gè)版本根據(jù)CanIUse.com追蹤的版本
not dead
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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