webpack4配置優化

前面咱們說了webpack的一些基礎,如今咱們來使用webpack實際來編譯寫個項目。
用vue-cli建立一個項目,而後把它的vue-cli-service以及webpack等黑盒工具移除,而後咱們來本身編譯它。
首先咱們要建立三個文件css

  • webpack.common.js 公共的webpack配置
  • webpack.dev.js 開發階段的配置
  • webpack.prod.js 生產階段的配置

首先咱們來編寫webpack.common文件html

const HtmlWebpackPlugin = require('html-webpack-plugin')// html模板
const webpack = require('webpack')
const PreloadWebpackPlugin = require('preload-webpack-plugin')// 預加載
const path = require('path')
const os = require('os')
const VueLoaderPlugin = require('vue-loader/lib/plugin')// vue對應插件
const workers = { // 多線程編譯
  loader: 'thread-loader',
  options: {
    workers: os.cpus().length
  }
}
module.exports = {
  // entry: ['babel-polyfill', './src/main.js'],
  entry: './src/main.js', // 入口
  output: {
    filename: 'bundle.js' // 輸出

  },
  optimization: {
    concatenateModules: true, // 儘量合併模塊到一個函數
    splitChunks: { // 公共代碼拆分
      // include all types of chunks
      chunks: 'all'
    },
    runtimeChunk: true // 運行時代碼  拆出來 緩存
  },
  resolve: {
    // modules: [//解析時搜索得模塊,配了致使ie報錯
    //     path.resolve(__dirname, 'node_modules'),
    // ],
    alias: { // 配置別名
      components: path.resolve(__dirname, 'src/components'),
      src: path.resolve(__dirname, 'src')
    },
    extensions: ['.js', '.json', '.vue'] // 省略後綴
  },
  module: {
    rules: [

      { // babel
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          workers,
          {
            loader: 'babel-loader',
            options: {
              // babel-polyfill按需引入,差了1m左右 core polyfill原型
              presets: [['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3 }]],
              cacheDirectory: true, // 開啓緩存 第二次構建時,會讀取以前的緩存,吃內存,不開發了記得清下佔用
              // presets: ['@babel/preset-env']
              plugins: [ // import()語法支持
                '@babel/plugin-syntax-dynamic-import'
              ]
            }
          }

        ]
      },

      {
        test: /\.(gif|png|jpe?g|svg)$/i, // 不超過10kb轉爲data:urlbase64
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10 * 1024, // 10 KB
              name: 'image/[name]-[contenthash:8].[ext]', // 輸出hash
              esModule: false
            }
          }]
      },

      {
        test: /\.vue$/, // 解析vue文件
        use: [
          workers,
          'vue-loader'
        ]
      }

    ]
  },
  plugins: [
    new PreloadWebpackPlugin({ // 加入預解析和預加載webpack4以後下載preload-webpack-plugin@next最新版
      rel: 'preload',
      as (entry) {
        console.log(entry)
        if (/\.css$/.test(entry)) return 'style'
        if (/\.woff$/.test(entry)) return 'font'
        if (/\.(png|jpe?g|gif)$/.test(entry)) return 'image'
        return 'script'
      },
      include: 'allChunks',
      fileBlacklist: [/\.(map)$/, /runtime~.+\.js$/]
      // 把runtime文件抽離出來,由於import()運行時文件變化了,runtime管理運行時babel導入文件的hash也會變化,
      // 默認webpack打包出來的依賴入口文件會把runtime導入,這樣會致使它的hash也會變化,這樣就會致使緩存失效,
      // 因此把runtime這個文件加載到html中,從以來入口文件中抽離,來避免依賴入口的hash變化。這樣它就不須要進行預加載了。
    }),
    new HtmlWebpackPlugin({ // html模板
      title: 'Vue dev App',
      template: path.resolve(__dirname, 'public/index.html')
    }),

    new VueLoaderPlugin(),
    new webpack.DefinePlugin({
      // 值要求的是一個代碼片斷
      BASE_URL: '"/"'
    })
  ]
}

代碼裏都有註釋,而後咱們作下思路分析:vue

  • 肯定打包造成依賴圖的入口文件和輸出文件名字
  • 配置別名resolve方便項目開始時快捷引入
  • 配置loader,首先解析vue文件須要vue-loader和vue-loader/lib/plugin插件,解析圖片使用url-loader配置大小(10kb如下轉換爲base64超出的copy圖片)和文件路徑以及文件名contenthash:8內容級別的hash,vue的圖片導入默認使用的CommonJs規範,因此標註esModule不轉爲esm,最後配置babel-loader轉換js特性,首先下載babel-core核心模塊和env編譯全部新特性,指定不須要編譯的文件夾,babel-polyfill配置上兼容新的api(Iterator、Generator、Set、Maps、Proxy、Reflect),而後配置useBuiltIns按瀏覽器缺乏的polyfill按需加載,而後配置core:3兼容 原型的方法(array.includes)(http://www.javashuo.com/article/p-xbxjctyp-dt.html

java

  • 而後咱們配置了workers,在第一次編譯後開啓多線程打包,只對可能影響編譯速度的loader添加。
  • 而後配置optimization屬性,儘量合併模塊到一個函數,拆分公共代碼,而後把運行時得代碼拆分出來(import()的代碼)
  • 而後配置插件,首先是預加載插件,html模板,vueLoader的插件,設置路徑變量。

而後再配置開發階段配置webpack.devnode

const { merge } = require('webpack-merge')// 合併配置插件
const common = require('./webpack.common')// 公共配置
const path = require('path')
const os = require('os')
const webpack = require('webpack')
const workers = { // 多線程編譯
  loader: 'thread-loader',
  options: {
    workers: os.cpus().length
  }
}
module.exports = merge(common, {
  mode: 'development',
  devtool: 'eval-cheap-module-source-map',
  // devtool:'source-map',
  devServer: {
    hot: true,
    contentBase: path.join(__dirname, 'public'),
    open: true,
    stats: 'errors-only',
    clientLogLevel: 'none',
    disableHostCheck: true
  },
  module: {
    rules: [
      {
        test: /\.(js|vue)$/, // eslint
        enforce: 'pre',
        exclude: /node_modules/,
        include: path.resolve(__dirname, 'src'),
        loader: [workers, 'eslint-loader']
      },
      {
        test: /\.less$/, // 解析引入得less
        use: [
          workers,
          'style-loader',
          'css-loader',
          'postcss-loader',
          'less-loader'
        ]
      },
      {
        test: /\.css$/, // 解析組件內部style
        use: [
          workers,
          'style-loader',
          'css-loader',
          'postcss-loader'
        ]
      }
    ]
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin() // 熱加載
  ]
})
  • 這個文件很簡單,咱們就須要合併下公共配置,而後加一些開發階段的特殊配置
  • 配置模式開發,配置source-map,配置devServer 服務熱加載,讀取資源目錄
  • 配置loader,首先下載eslint相關依賴,生產eslint配置,配置eslint-loader,less和css的loader,咱們這裏使用了postcss以及它的相關插件

postcss.config.jswebpack

const postcssConfig = {
  plugins: [ // 配合package的browserslist
    require('postcss-import'),
    require('postcss-cssnext'),
    require('cssnano')
  ]
}
console.log(process.env.NODE_ENV)
if (process.env.NODE_ENV === 'production') { // 使用postcss插件方式實現,webpack插件方式太麻煩了
  const purgecss = require('@fullhuman/postcss-purgecss')
  const purgecssConfig = require('./purgecss.config')
  postcssConfig.plugins.push(purgecss(purgecssConfig))
}
module.exports = postcssConfig

這個裏面引入了一個生產階段對vue內部style css搖樹的插件配置,配置我貼出來
purgecss.configgit

module.exports = {
  content: ['./public/**/*.html', './src/**/*.vue'], /// / 清理範圍,全部的vue component和和入口的html,js裏引入的默認是全局使用的
  defaultExtractor (content) {
    const contentWithoutStyleBlocks = content.replace(/<style[^]+?<\/style>/gi, '')
    return contentWithoutStyleBlocks.match(/[A-Za-z0-9-_/:]*[A-Za-z0-9-_/]+/g) || []
  },
  whitelist: [/el-.+$/], // 將elementuijs使用上的類也放在白名單不做處理
  whitelistPatterns: [/-(leave|enter|appear)(|-(to|from|active))$/, /^(?!(|.*?:)cursor-move).+-move$/, /^router-link(|-exact)-active$/, /data-v-.*/, /el-.+$/]
}
  • 最後配置一個熱加載的插件

最後咱們配置生產文件
webpack.prodweb

const { merge } = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')// 打包以前清空
const CopyWebpackPlugin = require('copy-webpack-plugin')// copy文件插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')// 把css抽成link的方式
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')// 壓縮css
const TerserWebpackPlugin = require('terser-webpack-plugin')// 壓縮js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin')// 匹配 runtime.chunk文件寫到html中
const common = require('./webpack.common')
const path = require('path')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') // webpack 打包結果分析
console.log(common)
// common.module.rules.push({
//     test: /\.(gif|png|jpe?g|svg)$/i,
//     //這個依賴下載要用 cnpm 淘寶源,而且用管理員權限下載,下不下來把依賴都刪了,而後從新下載,其餘都下不來,親身經歷
//    //算了不用它了,經過它壓縮事後的圖片包括base64,在ie中不能解析
//     loader: 'image-webpack-loader',
//     options: {
//         mozjpeg: {
//             progressive: true,
//         },
//         // optipng.enabled: false will disable optipng
//         optipng: {
//             enabled: false,
//         },
//         pngquant: {
//             quality: [0.65, 0.90],
//             speed: 4
//         },
//         gifsicle: {
//             interlaced: false,
//         },
//         // the webp option will enable WEBP
//         webp: {
//             quality: 75
//         }
//     }
// })
module.exports = merge(common, {
  mode: 'production',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash:8].bundle.js', // 輸出name hash
    chunkFilename: '[name].[contenthash:8].chunk.js' // chunk文件 hash
  },
  devtool: 'none',
  module: {
    rules: [
      {
        test: /\.(less)$/,
        use: [
          MiniCssExtractPlugin.loader, // 生成環境,最後不適用style插入,使用link方式插入
          // 'style-loader',
          'css-loader',
          'postcss-loader',
          'less-loader'
        ]
      },
      {
        test: /\.css$/, // 解析組件內部style
        use: [
          MiniCssExtractPlugin.loader,
          // 'style-loader',
          'css-loader',
          'postcss-loader'
        ]
      }
    ]
  },
  optimization: {
    minimizer: [
      new TerserWebpackPlugin({
        parallel: true// 開啓多線程
      }), // 壓縮js
      new OptimizeCssAssetsWebpackPlugin({
        cssProcessPluginOptions: {
          preset: ['default', { discardComments: { removeAll: true } }]
        }
      }) // 壓縮css
    ],

    // 模塊只導出被使用的成員
    usedExports: true,
    sideEffects: true // 標記無反作用,配合package js搖樹使用
  },
  plugins: [

    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Vue prod App',
      template: path.resolve(__dirname, 'public/index.html'),
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true
      }
    }),
    new MiniCssExtractPlugin({ // 生成link的css文件,名稱hash
      filename: '[name].[contenthash:8].css'
    }),
    new CopyWebpackPlugin([ // 複製文件
      'public'
    ]),
    new ScriptExtHtmlWebpackPlugin({ // 寫入html中
      inline: [/runtime~.+\.js$/] // 正則匹配runtime文件名
    }),

    new BundleAnalyzerPlugin() // webpack打包結果分析

  ]
})
// css搖樹使用postcss插件實現了
  • 老樣子配置模式,和輸出地址,這會的文件名稱就須要用到contenthash文件級別的hash生成,方便咱們作瀏覽器緩存(chunkFilename 文件的名字也作了hash)。
  • 去除source-map
  • 配置css相關loader最後轉換的時候咱們不使用style-loader方式用style標籤插入,而是使用link文件生成css文件引入
  • optimization配置壓縮js和css,usedExports標識導出使用的模塊,標記sideEffects無反作用配合package文件標識部分右反作用文件,來完成js搖樹
  • 最後就是插件,清空文件夾,html模板,生成css文件(不支持熱加載,生產階段使用,該插件的主要是爲了抽離 css 樣式,防止將樣式打包在 js 中文件過大和由於文件大網絡請求超時的狀況),複製文件,而後把runtime開頭的管理(import()babel)異步管理導入的文件,寫入到html中(爲何這樣common配置裏有寫,webpack相關的runtimeChunk又講相關知識)。上線優化方案
  • 最後就是添加一個webpack結果打包分析的插件

babel.config.js咱們有一個兼容[vue-cli的瀏覽器兼容babel的配置]vue-cli

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ]
}

最後貼的就是package.jsonnpm

{
  "name": "vue-app-base",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "cross-env NODE_ENV=development webpack-dev-server --config webpack.dev.js",
    "build": "cross-env NODE_ENV=production webpack --config webpack.prod.js",
    "lint": "eslint --ext .js,.vue src/",
    "all-lint-fix": "eslint --fix --ext .js,.vue src/",
    "precommit": "lint-staged"
  },
  "dependencies": {
    "core-js": "3",
    "vue": "^2.6.11"
  },
  "devDependencies": {
    "@babel/core": "^7.12.9",
    "@babel/plugin-syntax-dynamic-import": "^7.8.3",
    "@babel/preset-env": "^7.12.7",
    "@fullhuman/postcss-purgecss": "^3.0.0",
    "@vue/cli-plugin-babel": "^4.5.9",
    "babel-loader": "^8.2.1",
    "clean-webpack-plugin": "^3.0.0",
    "copy-webpack-plugin": "^5.0.4",
    "cross-env": "^7.0.3",
    "css-loader": "^5.0.1",
    "cssnano": "^4.1.10",
    "eslint": "^7.14.0",
    "eslint-config-standard": "^16.0.2",
    "eslint-loader": "^4.0.2",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-vue": "^7.1.0",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^4.5.0",
    "husky": "^4.3.0",
    "image-webpack-loader": "^7.0.1",
    "less": "^3.12.2",
    "less-loader": "^7.1.0",
    "lint-staged": "^10.5.2",
    "mini-css-extract-plugin": "^1.3.1",
    "optimize-css-assets-webpack-plugin": "^5.0.4",
    "postcss": "^8.1.10",
    "postcss-cssnext": "^3.1.0",
    "postcss-import": "^13.0.0",
    "postcss-loader": "^4.1.0",
    "preload-webpack-plugin": "^3.0.0-beta.4",
    "script-ext-html-webpack-plugin": "^2.1.5",
    "style-loader": "^2.0.0",
    "terser-webpack-plugin": "2.2.1",
    "thread-loader": "^3.0.1",
    "url-loader": "^4.1.1",
    "vue-loader": "^15.9.5",
    "vue-template-compiler": "^2.6.12",
    "webpack": "^4.41.2",
    "webpack-bundle-analyzer": "^4.1.0",
    "webpack-cli": "3.3",
    "webpack-dev-server": "^3.9.0",
    "webpack-merge": "^5.4.0"
  },
  "lint-staged": {
    "src/**/*.(js|vue)": [
      "eslint --fix",
      "git add"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "npm run precommit"
    }
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "babel-eslint"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "IE 10"
  ],
  "sideEffects": [
    "*.css",
    "*.less",
    "*.vue"
  ]
}

這樣就配置完成了,這一套配置其實去除掉vue相關的就是一套通用配置。
作下webpack 的loader和plugin區別

  • loader: loader雖然是擴展了 webpack ,可是它只專一於轉化文件(transform)這一個領域,完成壓縮,打包,語言翻譯。
    loader是運行在NodeJS中。
    僅僅只是爲了打包,僅僅只是爲了打包,僅僅只是爲了打包,重要 的話說三遍!!!
  • plugin:plugin也是爲了擴展webpack的功能,可是 plugin 是做用於webpack自己上的。並且plugin不只只侷限在打包,資源的加載上,它的功能要更加豐富。從打包優化和壓縮,到從新定義環境變量,功能強大到能夠用來處理各類各樣的任務。

b53923b5061b105189afce540edbb43.png

webpack的執行流程咱們能夠直接來參考這張圖來看。

相關文章
相關標籤/搜索