webpack配置

628 阅读9分钟

paths路径文件配置

const path = require('path');
const glob = require('glob');

class Path {
  constructor({
    sourceName, // 项目总入口
    pagesEntryName, // 页面总入口
    entryName, // 页面入口文件
    pageConfigName, // 页面配置文件
    sourceWideToEntry, // 页面总入口到页面入口文件的宽度
    templateEntryConfig, // 模板的配置文件
    templateEntry, // 模板
    dist // 输出文件位置
  }) {
    this.home = process.cwd();
    this.sourceName = sourceName;
    this.pagesEntryName = pagesEntryName;
    this.entryName = entryName;
    this.pageConfigName = pageConfigName;
    this.templateEntry = templateEntry;
    this.templateEntryConfig = templateEntryConfig;
    this.sourceWideToEntry = sourceWideToEntry;
    this.dist = dist;
  }

  distPath() {
    return path.resolve(this.home, this.dist);
  }

  sourcePath() {
    return path.resolve(this.home, this.sourceName);
  }

  pagesEntryPath() {
    return path.resolve(this.sourcePath(), this.pagesEntryName);
  }

  entryPath() {
    return path.resolve(
      this.pagesEntryPath(),
      this.sourceWideToEntry,
      this.entryName
    );
  }

  pageConfigPath() {
    return path.resolve(
      this.pagesEntryPath(),
      this.sourceWideToEntry,
      this.pageConfigName
    );
  }

  templateEntryPath() {
    return path.resolve(this.home, this.templateEntry);
  }

  templateEntryConfigPath() {
    return path.resolve(this.home, this.templateEntryConfig);
  }

  entryPaths() {
    // 所有页面入口文件的路径
    return glob.sync(this.entryPath());
  }

  pageConfigPaths() {
    // 所有页面配置文件的路径
    return glob.sync(this.pageConfigPath());
  }
}

module.exports = new Path({
  sourceName: 'src',
  pagesEntryName: 'pages',
  entryName: 'entry.js',
  sourceWideToEntry: '**',
  pageConfigName: 'page.js',
  templateEntry: 'template/index.html',
  templateEntryConfig: 'template/page.js',
  dist: 'dist'
});

entry (根据路径自动获取所有入口)

const entryConfigure = () => {
  const entries = {};
  // paths.entryPaths()为['/Users/pjm/Desktop/Components/src/pages/admin/entry.js', '/Users/pjm/Desktop/Components/src/pages/ticket/entry.js']
  // paths.pagesEntryPath()为'/Users/pjm/Desktop/Components/src/pages'
  paths.entryPaths().forEach((item) => {
    const result = path.relative(paths.pagesEntryPath(), item);
    const key = path
      .dirname(result)
      .split(/\\/g)
      .join("/");
    entries[key] = item;
  });
  // entries最终为 { admin: '/Users/pjm/Desktop/Components/src/pages/admin/entry.js', ticket: '/Users/pjm/Desktop/Components/src/pages/ticket/entry.js'}
  return entries;
};

module.exports = {
    entry: entryConfigure,
}
  • 根据pages与每一个页面入口entry.js的相对路径,来决定入口的键名(之后可以通过这个键名,来决定输出目录的结构)
  • src/pages 与 src/pages/admin/entry.js 之间的相对路径,就可以取得admin/entry.js
  • 再根据path.dirname,去掉文件名,最终可以拿到admin.

output

const paths = require('../paths.config');

module.exports = (production = false) => {
// paths.distPath() 为 '/Users/pjm/Desktop/Components/dist'
  return {
    output: production
      ? {
          path: paths.distPath(),
          filename: "[name]/index.[chunkhash].js",
          chunkFilename: "[name].[id].[chunkhash].js",
          // publicPath: `/`,
        }
      : {
          path: paths.distPath(),
          filename: "[name].js",
          chunkFilename: "[name].[id].js",
          publicPath: "/",
        },
  };
};
  • 在开发环境不需要hash,可以缓存的就缓存,减少请求
  • 在生产环境,entry中已经设定好键名了,那么,只要在filename中[name],就可以设定输出文件的路径了
  • chunkFilename可以决定,输出除了entry外的文件,比如splitChunks出来的文件

htmlWebpackPlugin

npm i html-webpack-plugin --save-dev使用方法

配置

const paths = require("../paths.config");
const fs = require('fs');
const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");

const MANIFEST = 'manifest';
const VENDOR = 'vendor';

// 用作合并html模板配置
const deepMerge = (...objs) => {
  let result = {};
  objs.forEach((obj = {}) => {
      Object.keys(obj || {}).forEach((key) => {
          if (result[key] === undefined) {
              // 第一次初始化的情况, 防止复制引用地址,用merge处理
              if (Array.isArray(obj[key])) {
                  result[key] = Object.values(merge(obj[key]));
              } else if (typeof (obj[key]) === 'object') {
                  result[key] = merge(obj[key]);
              } else {
                  // 普通数据直接覆盖值
                  result[key] = obj[key];
              }
          } else if (Array.isArray(result[key])) {
              // 先处理result有引用值的情况
              obj[key].forEach((value) => {
                  if (!result[key].includes(value)) {
                      result[key].push(value);
                  }
              });
          } else if (typeof(result[key]) === 'object') {
              result[key] = merge(result[key], obj[key]);
          } else {
              // 普通数据直接覆盖值
              result[key] = obj[key];
          }
      });
  });
  return result;
};

const handleHTMLPlugin = (key, myPage, myConfigs, production) => {
  const opts = {
    // 生成html的路径
    filename: path.join(key, 'index.html').split(/\\/g).join('/'),
    // html模板的路径
    template: myPage,
    // 生成的chunk不自动插入html中
    inject: false,
    // 生产环境生产的html删除空格和注释
    minify: production
      ? {
          removeComments: true,
          collapseWhitespace: true,
        }
      : false,
    // html是否缓存
    cache: true,
    // 增加的参数,可以在html中通过htmlWebpackPlugin.options.configs拿到里面的配置
    configs: myConfigs,
    // 生产环境配置splitCode,会生成manifest和vender文件
    // 这里的chunk如果上面的inject配置不为false就会自动插入生成的html中,如果为false,那么得通过htmlWebpackPlugin.files获取
    chunks: production ? [MANIFEST, VENDOR, key] : [key], 
    // chunk插入文件的顺序,手动
    chunksSortMode: "manual",
  };
  return new HtmlWebpackPlugin(opts);
}

// 传入的entries为入口配置对象
module.exports =(entries, production = false) => {
  return {
    plugins: [
      ...Object.entries(entries).map(([key, entryPath]) => {
        const dirName = path.dirname(entryPath);
        // html模板取全局还是局部
        const indexHTML = path.join(dirName, 'index.html');
        // paths.templateEntryPath()为'/Users/pjm/Desktop/Components/template/index.html'
        const myPage = fs.existsSync(indexHTML)
          ? indexHTML
          : paths.templateEntryPath();
        // html的配置文件取局部还是全局
        const indexConfig = path.join(dirName, 'page.js');
        // paths.templateEntryConfigPath() 为 '/Users/pjm/Desktop/Components/template/page.js'
        const globalPageConfig = require(paths.templateEntryConfigPath());
        const myConfigFile = fs.existsSync(indexConfig)
          ? require(indexConfig)
          : globalPageConfig;
        // 将当前html的配置和全局html的配置合并
        const myConfigs = deepMerge({}, globalPageConfig, myConfigFile);

        return handleHTMLPlugin(key, myPage, myConfigs, production);
      }),
    ],
  };
};

html模板

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <title>
      <%= htmlWebpackPlugin.options.configs.title %>
    </title>
    <link
      rel="shortcut icon"
      href="<%= htmlWebpackPlugin.options.configs.icon %>"
    />
    <meta
      name="viewport"
      content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"
    />
    <meta
      name="Copyright"
      content="<%= htmlWebpackPlugin.options.configs.copyright %>"
    />
    <meta
      name="Description"
      content="<%= htmlWebpackPlugin.options.configs.descriptions %>"
    />
    <meta
      name="Keywords"
      content="<%= htmlWebpackPlugin.options.configs.keywords %>"
    />
    <!--开始注入配置文件的css-->
    <% Array.isArray(htmlWebpackPlugin.options.configs.css) &&
    htmlWebpackPlugin.options.configs.css.forEach(function(css) { %>
    <link href="<%= css %>" rel="stylesheet" />
    <% }) %>
    <!--结束注入配置文件的css-->
    <!--开始注入webpack生成的css-->
    <% htmlWebpackPlugin.files.css.forEach(function(css) { %>
    <link href="<%= css %>" rel="stylesheet" />
    <% }) %>
    <!--结束注入webpack生成的css-->
    <!--开始注入配置文件的script-->
    <% Array.isArray(htmlWebpackPlugin.options.configs.script) &&
    htmlWebpackPlugin.options.configs.script.forEach(function(script) { %>
    <script type="text/javascript" src="<%= script %>"></script>
    <% }) %>
    <!--结束注入配置文件的script-->
  </head>
  <body>
    <div class="page">
      <div id="app"></div>
    </div>
    <!--开始注入webpack生成的js-->
    <% htmlWebpackPlugin.files.js.forEach(function(js) { %>
    <script
      type="text/javascript"
      src="<%= js %>"
    ></script>
    <% }) %>
    <!--结束注入webpack生成的js-->
  </body>
</html>

html模板htmlWebpackPlugin详细数据

通过在上面模板打印console.log(JSON.parse('<%= JSON.stringify(htmlWebpackPlugin)%>'))可以知道htmlWebpackPlugin可以获取哪些数据

{
  "tags":{
    "headTags":[],
    "bodyTags":[
      {
        "tagName":"script",
        "voidTag":false,
        "attributes":{"defer":false,"src":"../admin/index.a37714976896446bf1ac.js"}
      }
    ]
  },
  "files":{
    "publicPath":"../",
    // 通过配置的chunks生成的js文件都会到这
    "js":["../admin/index.a37714976896446bf1ac.js"],
    // 如果有配置mini-css-extract-plugin生成的css文件会在这里可以取到
    "css":[]
  },
  "options":{
    "template":"/Users/pjm/Desktop/Components/node_modules/html-webpack-plugin/lib/loader.js!/Users/pjm/Desktop/Components/template/index.html",
    "templateContent":false,
    "filename":"admin/index.html",
    "publicPath":"auto",
    "hash":false,"
    inject":false,
    "scriptLoading":"blocking",
    "compile":true,
    "favicon":false,
    "minify":{
      "removeComments":true,
      "collapseWhitespace":true
    },
    "cache":true,
    "showErrors":true,
    "chunks":["manifest","vendor","admin"],
    "excludeChunks":[],
    "chunksSortMode":"manual",
    "meta":{},
    "base":false,
    "title":"Webpack App",
    "xhtml":false,
    "configs":{
      "title":"webpack-devkit",
      "icon":"",
      "descriptions":"webpack-devkit",
      "keywords":"webpack-devkit",
      "css":["https://unpkg.com/element-ui/lib/theme-chalk/index.css"],
      "script":[
        "http://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js",
        "https://unpkg.com/element-ui/lib/index.js"
      ]
    }
  }
}

这样配置,可以控制,控制台输出的信息很简洁

thread-loader

npm i thread-loader --save-devthread使用方法

  • webpack同一时间只能执行一个任务,用了thread可以让webpack同一时间执行多个任务,处理完后再将结果返回主进程,其中HappyPack也是同理,但是HappyPack不在维护了,所以,推荐官网的thread-loader
  • 注意,需要在比较耗时的loader上使用,并且使用的loader不能产生新的文件
  • 具体用法在下面耗时loader有用到(babel,eslint...)

babel-loader

npm install --save-dev babel-loader @babel/core @babel/preset-env [babel使用方法]

  • @babel/core: babel的核心功能都在这里,没有它,就不能使用babel编译
  • @babel/preset-env: 预设插件,就是这里面包含一系列插件,不用我们一个个安装,它会根据我们所编写的代码和设定的浏览器来进行处理,所以说,如果我们的代码只运行在浏览器环境,那么,最好通过.browserslistrc文件来指定浏览器,这样防止采取默认兼容了所有的浏览器
  • 注意:插件分语法插件,转换插件,转换插件是用来协助对应的语法插件的,所以,使用转换插件必须配合对应的语法插件,但不必指定二种插件,只需指定转换插件,就可以调用语法插件了。语法转换只是将高版本语法转换成低版本语法,而新的内置函数和实例方法低版本没有,即无法转换,所以需要垫片(polyfill或runtime)来填充(www.babeljs.cn/docs/)
const babelLoaderConfigure = () => {
  return {
    test: /\.js$/,
    exclude: /node_modules/,
    use: [
      'thread-loader',
      { loader: 'babel-loader', options: { cacheDirectory: true } }
    ]
  };
};

web应用开发

npm install --save core-js@3

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": 3
      }
    ]
  ]
}
  • @babel-polyfill: 完整模拟2015ES+环境,但是需要全局引入,使打包后的文件变得非常大,也会对全局环境造成污染,也会像一些类的原型上增加方法,导致整体变得不可控
  • 从Babel7.4开始@babel/polyfill就不推荐使用了,因为polyfill本身也就是core-js与regenerator-runtime这二个包的集合,所以引入这2个包也行,core-js是javascript标准库的polyfill,尽可能的模块化,让你选择你需要的功能,默认引入的是core-js@2,但是分支已经封锁,之后的更新都会到core-js@3中,所以,我们使用core-js@3就行
  • 在@babel/preset-env配置中有useBuiltIns和corejs两个属性,可以来控制polyfill的引入,使用useBuiltIns有三种分别为false,entry,usage。false:手动引入,打包全部。entry:根据入口文件全部引入,usage:按需引入,如果使用了useBuiltIns,但是未指定corejs,那么就会报错.
  • 以上配置方式,都会往我们代码中注入辅助函数,导致全局环境的污染,但是不会改造本身的代码,例如以下使用class时,就会注入辅助函数_classCallCheck, _defineProperties, _createClass
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  
  function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
  
  function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
  
  var a = Object.assign({}, {});
  console.log(a);
  [(1, 5, 10, 15)].filter(function (value) {
    return value > 9;
  });
  var promise = new Promise(function (resolve, reject) {
    resolve(1);
  });
  
  var A = /*#__PURE__*/function () {
    function A() {
      _classCallCheck(this, A);
    }
  
    _createClass(A, [{
      key: "say",
      value: function say() {
        console.log('hello');
      }
    }]);
  
    return A;
  }();

框架,类库开发

npm install --save @babel/runtime
npm install @babel/runtime-corejs3 --save
npm install --save-dev @babel/plugin-transform-runtimeplugin-transform-runtime使用方法

{
  "presets": ["@babel/preset-env"],
  "plugins": [
    [
      "@babel/transform-runtime",
      {
        "corejs": 3
      }
    ]
  ]
}
  • @babel/runtime:这个是辅助函数的集合包,在引用这个的时候,不会导致全局污染.
  • @babel/plugin-transform-runtime: 移除辅助函数,替换成@babel/runtime/helpers中函数的引用,同时避免冗余引入减小打包体积,
  • 会将我们本身代码进行改造 .browserslistrc文件
defaults
IE >= 9
Chrome >= 43
ChromeAndroid >= 43
Android >= 4.1
iOS >= 13

eslint-loader

npm i --save-dev eslint-loader
npm i --save-dev babel-eslint
npm i --save-dev standard
npm i --save-dev eslint-plugin-prettier eslint-config-prettierprettier使用方法 配合其他插件用法
npm i --save-dev eslint-plugin-vue使用方法

const eslintLoaderConfigure = () => {
  return {
    enforce: 'pre',
    test: /\.(js|vue)$/,
    exclude: /node_modules/,
    use: ['thread-loader', 'eslint-loader']
  };
};

.eslintrc.js文件

module.exports = {
  root: true,
  parserOptions: {
    parser: 'babel-eslint'
  },
  env: {
    browser: true,
    node: true
  },
  extends: [
    'standard',
    'plugin:vue/recommended',
    'plugin:prettier/recommended',
    "prettier/standard",
    'prettier/vue'
  ],
  rules: {
    'vue/component-tags-order': [
      'error',
      {
        order: ['template', 'script', 'style']
      }
    ]
  }
};

.prettierc文件

{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "none"
}

  • plugin:prettier/recommended: 在extend中,写了前面的plugin就可以不用再plugin中加这个插件了,这个写法相当于已经引用了
  • eslint-config-prettier: 这个可以让其他规则不会与prettier冲突,但,这个必须放在extends的最后,每一个eslint规则,都可以用一个与之对应的,来解决冲突,如plugin:vue/recommended与prettier/vue
  • babel-eslint: 指定这个解析器,用来支持es6语法.

externals

module.exports = {
  externals: {
    vue: "Vue",
    'element-ui': 'ELEMENT',
  },
}

  • 这个为外部扩展的内容,需要声明后,才能在项目的上下文中使用,并且排除打包

resolve

module.exports = {
  resolve: {
    alias: {
      src: paths.sourcePath()
    },
    extensions: [".js", ".json", ".vue"],
  }
}
  • resolve都是跟解析有关的,而alias较为常用,是使用别名去代替某段路径,如上面写的那样,我们使用src代替了根路径到文件入口这段路径(绝对路径)
  • extensions(简化路径文件后缀名)

progress-bar-webpack-plugin

npm i --save-dev progress-bar-webpack-plugin使用方法
npm i --save-dev chalk

const ProgressBarPlugin = require("progress-bar-webpack-plugin");
const chalk = require("chalk");

module.exports = {
  plugins: [
      new ProgressBarPlugin({
      format: `  build [:bar]  ${chalk.green.bold(
        ":percent"
      )} (:elapsed seconds) :msg`,
      clear: true,
    }),
  ]
}
  • 一个好看的编译进度显示,效果图如下

clean-webpack-plugin

npm i --save-dev clean-webpack-plugin使用方法

const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  plugins: [
    new CleanWebpackPlugin({
      cleanOnceBeforeBuildPatterns: ['**/*', paths.distPath()]
    }),
  ]
}
  • 清理输出文件,默认情况下清理output.path指定的文件夹内的内容
  • 但是如果dist文件夹内有自己的目录结构不止一层结构,那么,就需要指定cleanOnceBeforeBuildPatterns去清理了,不然会报错,或者配置dry:true模拟文件的删除

url-loader and file-loader

npm i --save-dev url-loader file-loader使用方法

const imageLoaderConfigure = () => {
  return {
    test: /\.(png|jpe?g|gif|svg)$/i,
    use: [
      {
        loader: 'url-loader',
        options: {
          outputPath: 'images',
          name: '[name].[hash:7].[ext]',
          limit: 10000
        }
      }
    ]
  };
};
const fontsLoaderConfigure = () => {
  return {
    test: /\.(woff2?|eot|ttf|otf)$/i,
    loader: 'url-loader',
    options: {
      limit: 10000,
      name: '[name].[hash:7].[ext]',
      outputPath: 'fonts'
    }
  };
};

module.exports = {
  module: {
    rules: [
      imageLoaderConfigure(),
      fontsLoaderConfigure()
    ],
  },
}
  • url-loader可以根据limit,将小于设定这个值的,变为base64,插入js文件中,但是如果大于设定值,那么就会使用file-loader,将对应的文件单独输出到outputPath设定的位置

cross-env

npm i --save-dev cross-env 使用方法

  "scripts": {
    "start": "cross-env NODE_ENV=development webpack-dev-server --config ./build/webpack.config.js",
    "build": "cross-env NODE_ENV=production webpack --config ./build/webpack.config.js"
  }
  • 使用cross-env可以跨平台的设置环境变量

webpack-merge

npm i --save-dev webpack-merge使用方法

const { merge } = require("webpack-merge");
const devConfig = require("./webpack-config/webpack.dev");
const prodConfig = require("./webpack-config/webpack.prod");
const htmlConfig = require("./webpack-config/webpack.html");
const cssConfig = require("./webpack-config/webpack.css");
const outputConfig = require("./webpack-config/webpack.output");

const isProduction = process.env.NODE_ENV === "production";

module.exports = () => {
  const config = isProduction ? prodConfig : devConfig;
  return merge(
    baseConfig,
    config,
    htmlConfig(entryConfigure(), isProduction),
    cssConfig(isProduction),
    outputConfig(isProduction)
  );
};
  • 将传入的配置合并,使用这个方便了环境和配置的拆分

mini-css-extract-plugin与css-loader(打包css)

npm i --save-dev css-loader style-loader less less-loader postcss-loader
npm i --save-dev mini-css-extract-plugin 使用方法

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = (production = false) => {
  return {
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            production ? MiniCssExtractPlugin.loader : "style-loader",
            "css-loader",
            "postcss-loader",
          ],
        },
        {
          test: /\.less$/,
          use: [
            production ? MiniCssExtractPlugin.loader : "style-loader",
            "css-loader",
            "postcss-loader",
            "less-loader",
          ],
        },
      ],
    },
    plugins: production
      ? [
          new MiniCssExtractPlugin({
            filename: "[name]/index.[contenthash].css",
            // splitChunk打包的css会用这个命名
            chunkFilename: "[name].[id].[contenthash].css",
          }),
        ]
      : [],
  };
};
  • 这里postcss-loader(兼容浏览器,自动补全对应浏览器前缀),通过新建文件.postcssrc.js,来进行配置,并且,通过.browserslistrc指定了需要兼容的浏览器

.postcssrc.js文件

module.exports = {
  plugins: [['postcss-preset-env']]
};

.browserslistrc文件

defaults
IE >= 9
Chrome >= 43
ChromeAndroid >= 43
Android >= 4.1
iOS >= 13

optimize-css-assets-webpack-plugin(压缩css)

const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = {
  mode: "production",
  optimization: {
    minimizer: [
      new OptimizeCssAssetsPlugin({})
    ],
  }
 }
  • 使用optimize-css-assets-webpack-plugin,可以将输出的css最小化处理

terser-webpack-plugin(压缩js)

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  mode: "production",
  optimization: {
   // 配置TerserPlugin后需要将这个设置为true
    minimize: true,
    minimizer: [
      new OptimizeCssAssetsPlugin({}),
      new TerserPlugin({
        sourceMap: false,
        cache: true,
        parallel: true
      })
    ],
  }
 }
  • 因为在默认配置中,是在minimizer配置的压缩js,生产模式默认开启了,但是配置压缩css后,这里重写了,需要将压缩js也重写,这里使用terser-webpack-plugin来进行压缩css,其中cache做了缓存,在内存中该js没变化时,直接拿,parallel为开启多线程压缩.(uglifyjs-webpack-plugin也可以压缩js,但是已经不维护了,所以不做考虑)

vue-loader

npm i --save-dev vue-loader vue-template-compiler使用方法

const { VueLoaderPlugin } = require("vue-loader");

const vueLoaderConfigure = () => {
  return {
    test: /\.vue$/,
    exclude: /node_modules/,
    use: ['thread-loader', 'vue-loader']
  };
};

module.exports = {
  module: {
    rules: [
      vueLoaderConfigure()
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
  ],
}
  • 需要在plugins里面引入new VueLoaderPlugin(),这个可以帮助其它loader根据规则作用到相应的模块中,如,匹配/\.js$/规则的除了作用到普通.js文件还作用到<script>中,匹配/\.css$/会同理作用到普通的.css文件及作用到.vue文件的<style>
  • vue-style-loader与style-loader功能类似,但是如果做服务端渲染,需要vue-style-loader

splitChunks(拆分代码)

module.exports = {
  mode: "production",
  optimization: {
    splitChunks: {
      // async 表示只从异步加载得模块(动态加载import())里面进行拆分,
      // initial 表示只从入口模块进行拆分, 
      // all包含上面二种
      chunks: "all",
      // 生成的chunk大小,最小值不得小于20000字节
      minSize: 20000,
      // 需要分割时,最少引用次数
      minChunks: 1,
      // 按需加载的最大并行请求书,当按需加载的文件,拆分的文件大于5了就不继续拆分出新文件了,拆分优先级是哪个包大就先拆哪个
      maxAsyncRequests: 5,
      // 入口点的最大并行请求数,也就是入口点的拆分文件不能大于3,当等于3就不继续拆分出新文件了,拆分优先级是哪个包大就先拆哪个
      maxInitialRequests: 3,
      // 生成文件名的分隔符
      automaticNameDelimiter: "~",
      // 缓存组,继承或者覆盖上面的配置,默认还有vendors与default二个组,优先级分别为-10,-20,优先级相同的有限匹配先定义的
      cacheGroups: {
        default: {
          // 符合此配置生成的chunk文件名为manifest
          name: "manifest",
          chunks: "initial",
          minChunks: 2,
          // 数值越大,优先级越高
          priority: 10,
          // 多次引入的不会重复打包,直接复用
          reuseExistingChunk: true,
        },
        defaultVendor: {
          name: "vendor",
          // 限定作用范围
          test: /node_modules/,
          chunks: "initial",
          priority: 20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

详细打包分析链接

  • splitChunks将指定规则所匹配到的代码,提取到新的文件中(除了js同样适用于css)
  • cacheGroups组里面的元素,可以覆盖和继承外面的配置
  • 指定的name值会根据output中配置的chunkFilename,去设定文件名
  • 第二个缓存组里面的配置是将满足公共条件且应用到的/node_modules/里面的同步执行的代码提取出来,这个优先级需要大于-20,因为默认有个vendors组也是匹配node_modules里面的
  • 第一个缓存组里面是匹配所有代码,满足公共条件,被引用2次以上的同步执行的代码提取出来,reuseExistingChunk是配置了当某一模块被提取出来了,就重用,不会重复提取

devServer

const webpack = require('webpack');

module.exports = {
  mode: 'development',
  devServer: {
    // 为所有服务启用gzip压缩
    compress: true,
    // 启用服务地址
    host: '127.0.0.1',
    // 启用端口
    port: '8090',
    // 开启HMR
    hot: true,
    // 当404时,展示index.html
    historyApiFallback: true,
    // 控制台打印内容,关闭
    clientLogLevel: 'silent',
    // 当设置为真时,此选项绕过主机检查, 解决代理问题
    disableHostCheck: true,
  },
  plugins: [new webpack.HotModuleReplacementPlugin({})]
}
  • clientLogLevel控制浏览器打印的信息,silent选项关闭打印
  • 需要开启热更新需要将hot: true,并且设置new webpack.HotModuleReplacementPlugin({}),之后,在代码中需要配合类似下面代码,才能热更新,类似Vue就在框架里面做了.
if (module.hot) {
  module.hot.accept('src/lib/util', function () {
    console.log('监听文件改变了!!!');
  });
}

stats

const statsConfigure = () => {
  return {
    colors: true, // 输出不同颜色
    modules: false, // 不展示已构建模块的详细信息
    children: false, // 不展示已构建模块的子详细信息
    chunks: false, // 不展示生成的chunk详细信息
    timings: true, // 展示时间信息
    chunkModules: false, // 不展示已构建模块添加进生成chunk里面的信息
    entrypoints: false // 不展示入口信息
  };
};
  • 输出一个简洁的构建信息

eslint + husky + lint-staged + prettier (统一代码规范)

npm i --save-dev husky 使用方法
npm i --save-dev lint-staged 使用方法

  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{js,vue}": [
      "eslint",
      "git add"
    ]
  }
  • husky可以调用git钩子,例如上面pre-commit,在commit的时候调用lint-staged
  • lint-staged会将目前在暂存区中的代码进行操作,如上面,先eslint检查,没问题就保存提交,有问题就终止然后报错