后面咱们说了webpack的一些根底,当初咱们来应用webpack理论来编译写个我的项目。
用vue-cli创立一个我的项目,而后把它的vue-cli-service以及webpack等黑盒工具移除,而后咱们来本人编译它。
首先咱们要创立三个文件

  • webpack.common.js 公共的webpack配置
  • webpack.dev.js 开发阶段的配置
  • webpack.prod.js 生产阶段的配置

首先咱们来编写webpack.common文件

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: '"/"'    })  ]}

代码里都有正文,而后咱们做下思路剖析:

  • 确定打包造成依赖图的入口文件和输入文件名字
  • 配置别名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)(https://www.cnblogs.com/dh-dh/p/10071312.html

  • 而后咱们配置了workers,在第一次编译后开启多线程打包,只对可能影响编译速度的loader增加。
  • 而后配置optimization属性,尽可能合并模块到一个函数,拆分公共代码,而后把运行时得代码拆分进去(import()的代码)
  • 而后配置插件,首先是预加载插件,html模板,vueLoader的插件,设置门路变量。

而后再配置开发阶段配置webpack.dev

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.js

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.config

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.prod

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')// 压缩cssconst TerserWebpackPlugin = require('terser-webpack-plugin')// 压缩jsconst 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的配置]

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

最初贴的就是package.json

{  "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不仅只局限在打包,资源的加载上,它的性能要更加丰盛。从打包优化和压缩,到从新定义环境变量,功能强大到能够用来解决各种各样的工作。

webpack的执行流程咱们能够间接来参考这张图来看。