Skip to content
on this page

扫码联系

编程学习&& IT

tian

模块化&&webpack

1. 模块化

    1. 全局function模式

首先在module文件下创建index.js 在其内创建test| test2将test函数暴露挂在window上==>则在index.html引入js即可访问到

    1. namespace模式
js
 let myModule = {
 data: 'test',
 foo() {
 console.log('foo');
 }
 }

image.png

  • 缺点==> 使用Jquery则出现如下问题
    index.js中导入juqery参数,但在index.html中使用则需要

      1. 单独引入jquery.js文件资源
      1. 依赖多个模块 若这里为n个js导入脚本 ==> 发起n次请求 ==> 请求过多 且 模块之间互相依赖关系不能够明确
      1. 若引入的顺序写反==>报错 (因为没有导入jquery文件无法正常使用index.js中暴露出来函数功能)
    1. IIFE模式
      匿名函数自调用
    1. IIFE增强
    1. 模块化规范
    • commonjs 针对node体系
    js
    //暴露模块
    module.exports={}
    // 引用模块 
    require('xxx')
    
    1. AMD CMD ES6 UMD
js
// es6 
// 基本使用 export 
import xx from xx

两者区别

  • commonjs输出的是一个值的拷贝 运行时加载
  • es6 值的引用 编译式加载 为了在编译时确定模块的关系

UMD(统一模块化定义规范,让代码模块在js所有环境中发挥作用)

1、CMD和AMD都是为了JavaScript模块化开发的规范

2、CMD是seajs推广过程中对模块定义的规范化产出; AMD是require,s推广过程中对模块定义的规范化产出

3、AMD是异步模块定义的意思,他是一个在浏览器端模块开发规范,由于不是JS原生支持,使用AMD规范进行页面开发时,需要对应的函数库

4、require.is解决的问题,多个JS文件可以有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器,JS加载的时候浏览器停止页面渲染,加载文件越多,页面失去响应时间越长

5、CMD通用模块定义,是国内发展的,有浏览器实现Sea.s,Sea.js要解决的问题和require,js一样,只不过模块定义的方式和模块加载时机有所不同

6、CMD 推崇依赖就近,AMD 推崇依赖前置

2. webpack 核心

特点 ---万物皆模块

  • Entry 入口

入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。 进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。 每个依赖项随即被处理,最后输出到称之为 bundles 的文件中。

  • Output

output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。 基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。

  • Module

模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。

  • Chunk

代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。

  • Loader

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。 loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。 本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。

  • Plugin

loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。 插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。

1. webpack 构建流程

Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程 :

  1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。

  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译。

  3. 确定入口:根据配置中的 entry 找出所有的入口文件。

  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。

  5. 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。

  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会。

  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。 在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。

2. 入门webpack配置

2.1 初始化项目

新建一个目录,初始化

js
npm init -y

webpack是运行在node环境中的,我们需要安装以下两个npm包

  • npm i -D 为npm install --save-dev的缩写
  • npm i -S 为npm install --save的缩写

新建一个build文件夹,里面新建一个webpack.config.js

js
// webpack.config.js

const path = require('path');
module.exports = {
    mode:'development', // 开发模式
    entry: path.resolve(__dirname,'../src/main.js'),    // 入口文件
    output: {
        filename: 'output.js',      // 打包后的文件名称
        path: path.resolve(__dirname,'../dist'),  // 打包后的目录
        clean: true,
    }
}

更改我们的打包命令

js
 "scripts": {
    "build": "webpack --config build/webpack.config.js"
  },

3. 安装如下依赖

js
npm install --save-dev @babel/core@^7.20.5 @babel/generator@^7.20.5 @babel/parser@^7.20.5 @babel/plugin-transform-runtime@^7.19.6 @babel/preset-env@^7.20.2 @babel/traverse@^7.20.5 @babel/types@^7.20.5 autoprefixer@^10.4.13 babel-loader@^9.1.0 css-loader@^6.7.2 html-webpack-plugin@^5.5.0 less@^4.1.3 less-loader@^11.1.0 mini-css-extract-plugin@^2.7.2 postcss-loader@^7.0.2 style-loader@^3.3.1 webpack@^5.75.0 webpack-cli@^5.0.1 webpack-dev-server@^4.11.1

3. 配置Html模板

为了将打包的Js放到页面来更好的观察用来自动生成html模板

3.1 基础配置

js
// webpack.config.js 配置 
const path = require('path');
//  引入自动生成html插件
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: "development", // production 开发模式
  entry: path.resolve(__dirname, '../src/main.js'), // 入口文件
  output: {
    filename: '[name].[hash:8].js', // 打包后文件名称 这里使用hash-->不同的名称
    path: path.resolve(__dirname, '../dist'), // 打包后的目录
    clean: true, // 优先清除之前打包的dist 再将打包内容输入 
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html'),
      title: 'webpack指定html测试'
    })
  ]
}

image.png

3.2 多个入口文件开发

生成多个html-webpack-plugin实例即可完成

js
// webpack.config.js 配置 
const path = require('path');
//  引入自动生成html插件
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: "development", // production 开发模式
  entry: path.resolve(__dirname, '../src/main.js'), // 入口文件
  output: {
    filename: '[name].[hash:8].js', // 打包后文件名称 这里使用hash-->不同的名称
    path: path.resolve(__dirname, '../dist'), // 打包后的目录
    clean: true, // 优先清除之前打包的dist 再将打包内容输入 
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html'),
      title: 'webpack指定html测试',
      filename: 'index.html',
      chunks: ['main'],
    }),
    new HtmlWebpackPlugin({
      filename: 'header.html',
      chunks: ['header'],
    }),
  ]
}

image.png

3.3 自定义HTML注入变量

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
</body>
</html>

htmlWebpackPlugin配置项

  • template
  • filename
  • chunks
  • title
  • inject script文件插入到HTML中的位置 默认为true
  • minifiy:true // development--默认不压缩 production--默认压缩
js
plugins:[
  new HtmlWebpackPlugin({
    template:path.resolve(__dirname,'../public/index.html'),
    filename:'index.html',
    chunks:['main'],
    title:"main",
    inject:'body',
  }),
  new HtmlWebpackPlugin({
    template:path.resolve(__dirname,'../public/header.html'),
    filename:'header.html',
    chunks:['header'],
    title:"header123",
    minify:true,
  })
]

devServer

js
yarn add webpack-dev-server -D
"scripts": {
  "build": "webpack  --config build/webpack.config.js"
},

4. 配置CSS

4.1 基本配置

js
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['css-loader'],
      }
    ]
  },

main.js中 ---手动挂载

js
// 引入css文件
import css from './style/base.css';

console.log(css);
console.log('main');

// let body = document.getElementsByTagName('body')[0];
// let style = document.createElement('style');
// style.innerText = css[0][1];
// body.appendChild(style);

只使用css-loader,它只负责打包处理css文件 分析各个css之间的依赖关系,返回解析的css内容 image.png

若想要--自动挂载 则使用style-loader, 它会将css-loader生成的css代码挂载到页面header部分

js
module: {
  rules: [
    {
      test: /\.css$/,
      use: ['style-loader', 'css-loader'],
    }
  ]
},

image.png

产生问题

WARNING

如果引入很多的css样式,经过css-loader转化 都会变为内联样式 ---> 体积非常大 会将css文件打包在main.js中 --> main.js体积也非常大 加载js时 导致时间非常的久 此时则需要通过拆分CSS

4.2 拆分CSS

将CSS单独的抽取出来加载 这也是性能优化的一种手段。 webpack4.0后, 官方推荐的mini-css-extract-plugin插件来打包css文件,此时需要去掉style-loader, 两者共存则会冲突(鱼和熊掌不可兼得也)

js
// 引入 css拆分插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'], // 解析顺序原则 右-->左
      },
      {
        test: /\.less$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[hash].css',
      chunkFilename: '[id].css',
    })
  ]

image.png

5. 打包图片、字体、媒体等文件

file-loader就是将文件在进行一些处理后(主要是处理文件名和路径、解析文件url),并将文件移动到输出的目录中.

url-loader 一般与file-loader搭配使用,功能与 file-loader 类似,如果文件小于限制的大小。则会返回 base64 编码,否则使用 file-loader 将文件移动到输出的目录中.

js
// webpack.config.js
module.exports = {
  // 省略其它配置 ...
  module: {
    rules: [
      // ...
      {
        test: /\.(jpe?g|png|gif)$/i, //图片文件
        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: 'fonts/[name].[hash:8].[ext]'
                }
              }
            }
          }
        ]
      },
    ]
  }
}

6. 用babel转义JS文件

为了使我们的js代码兼容更多的环境我们需要安装依赖

  • 注意 babel-loader与babel-core的版本对应关系

1.babel-loader 8.x 对应babel-core 7.x

2.babel-loader 7.x 对应babel-core 6.x

Babel其实是几个模块化的包:

  • @babe-l/core: babel核心库
  • babel-loader: webpack的babel插件,让我们可以在webpack中运行babe
  • @babel/preset-env: 将ES6转换为向后兼容的JavaScript,一组预先设定的插件 默认支持所有最新的JS(ES2015,ES2016等) 特定
  • @babel/plugin-transform-runtime: 处理async,await、import()等语法关键字的帮助函数
js
yarn add @babel/core babel-loader @babel/preset-env @babel/plugin-transform-runtime -D

配置如下

js
// `touch .babelrc`模块化包引入
{
  "presets": ["@babel/preset-env"],
  "plugins": ["@babel/plugin-transform-runtime"]
},

// webpack.config.js
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'], // 解析顺序原则 右-->左
      },
      {
        test: /\.less$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
      },
      {
        test: /\.jsx|\.js$/,
        use: ['babel-loader'],
        exclude: /node_modules/,
      }
    ]
  },
js
// main.js

function test() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1234);
    }, 0);
  });
}

async function main() {
  const result = await test();
  console.log(result);
}

main();

7. 区分开发环境与生产环境

安装 webpack-merge 用于配置合并

js
yarn add -D webpack-merge

提取webpack.common.js

js
// webpack.common.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { VueLoaderPlugin } = require('vue-loader'); // vue加载器
const FileListPlugion = require('../plugin/index');

module.exports = {
  // entry: path.resolve(__dirname, '../src/main.js'), // 入口文件
  entry: {
    main: path.resolve(__dirname, '../src/main.js'),
    header: path.resolve(__dirname, '../src/header.js'),
  },
  output: {
    filename: '[name].[hash:8].js', // 打包后的文件名称
    path: path.resolve(__dirname, '../dist'), // 打包后的目录
    clean: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html'),
      filename: 'index.html',
      chunks: ['main'],
      title: 'main',
      inject: 'body', // script文件插入到HTML中的位置 默认true
    }),
    new HtmlWebpackPlugin({
      filename: 'header.html',
      chunks: ['header'],
      title: 'header22',
      minify: true, // development 默认不压缩 production 默认压缩
    }),
    new MiniCssExtractPlugin({
      filename: '[name].[hash].css',
      chunkFilename: '[id].css',
    }),
    new FileListPlugion(),
    new VueLoaderPlugin(),
  ],
  resolve: {
    // 别名
    alias: {
      '@': path.resolve(__dirname, '../src'),
    },
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: ['vue-loader'], // 解析.vue模板
        include: [path.resolve(__dirname, '../src')],
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'], // 从右往左解析原则
      },
      {
        test: /\.less$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
      },
      {
        test: /(\.jsx|\.js)$/,
        use: [
          'babel-loader',
          // path.resolve(__dirname, '../loader/drop-console.js'),
        ],
        exclude: /node_modules/,
      },
    ],
  },
};

webpack.dev.js 开发环境配置文件

js
// 开发环境主要实现的是热更新,不要压缩代码,完整的sourceMap

const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const path = require('path');

module.exports = merge(common, {
  mode: 'development',
  devServer: {
    hot: true, // 热更新
    open: true,
    static: {
      // 告诉服务器从哪里提供内容。只有在你希望提供静态文件时才需要这样做。
      directory: path.resolve(__dirname, './public'),
    },
  },
});

webpack.prod.js 生产环境配置文件

js
// 生产环境主要实现的是压缩代码、提取css文件、合理的sourceMap、分割代码
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'production',
});

8. DIY webpack loader

loader 本质上就是Node的一个模块, 我们可以对其进行DIY开发, 例如 ,在日常开发时,控制台会console.log打印数据等,但是一个个去删除有特别麻烦, 因此通过loader可以删除console

AST文章AST快速入门

js
npm i -D @babel/parser @babel/traverse @babel/generator @babel/types
  • @babel/parser 将源代码解析成 AST
  • @babel/traverse 对AST节点进行递归遍历,生成一个便于操作、转换的path对象
  • @babel/generator 将AST解码生成js代码
  • @babel/types通过该模块对具体的AST节点进行进行增、删、改、查

8.1 编写原则

  • 单一原则 每个loader 只做一件事
  • 链式调用 webpack 会顺序链调用每一个 loader
  • 统一原则 输入或输出都是字符串 即插即用

🍏 实现思路

  1. input --> @babel/parser --> ast
  2. ast --> @babel/traverse --> new ast
  3. new ast --> @babel/generator --> output

@babel/types 是对node节点进行分析

创建loader文件夹--> drop-console.js

js
//
//去除掉代码中的console.log
// ast 

const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generator = require('@babel/generator').default;
const t = require('@babel/types');

module.exports = function (source) {
  const ast = parser.parse(source, { sourceType: 'module' });

  traverse(ast, {
    CallExpression(path) {
      if (t.isMemberExpression(path.node.callee) &&
        t.isIdentifier(path.node.callee.object, { name: 'console' })
      ) {
        path.remove();
      }
    }
  })
  const output = generator(ast, {}, source);
  return output.code;
};

配置如下

js
// webpack.config.js
module: {
   rules: [
     {
       test: /\.css$/,
       use: [MiniCssExtractPlugin.loader, 'css-loader'], // 解析顺序原则 右-->左
     },
     {
       test: /\.less$/,
       use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
     },
     {
       test: /\.jsx|\.js$/,
       use: [
        'babel-loader',
         path.resolve(__dirname, '../loader/drop-console.js'),
       ],
       exclude: /node_modules/,
     }
   ]
 },

实际上在webpack4中已经集成了去除console功能,在minimizer中可配置 去除console 附上官网 如何编写一个loader

9. Plugin

根据单一原则 loader只能一件事, 例如 less-loader只能解析less文件,plugin则针对整个流程执行广泛的任务

这里手写一个MyPlugin插件如下

js
//  在plugin/index.js下

// 输出文件名+大小
class MyPlugin {
  constructor() { }
  apply(compiler) {
    // compiler.hooks.done.tap('MyPlugin', (context, entry) => {
    //   console.log('编译完成');
    // })
    // compilation 拿到当前编译过程的内容
    compiler.hooks.emit.tap('MyPlugin', (compilation) => {
      let assets = compilation.assets;
      let content = '';
      Object.entries(assets).forEach(([filename, stateObj]) => {
        content += `文件名: ${filename}  文件大小: ${stateObj.size()} \n`;
      });
      console.log(content);
    })
  }
}

module.exports = MyPlugin;

配置

js
// 引用plugin
const MyPlugin = require('../plugin/index');
    new MyPlugin(),

image.png

10. 优化 webpack配置

10.1 编译进度条设置

一般来说,中型项目的首次编译时间为 5-20s,没个进度条等得多着急,通过 progress-bar-webpack-plugin插件查看编译进度,方便我们掌握编译情况。

js
yarn add progress-bar-webpack-plugin -D
yarn add chalk@4.1.2 -D // chalk 5 是esm写法 cjs无法使用
js
// webpack.config.js
// 引入打包进度条
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const chalk = require('chalk');
// .....
    new ProgressBarPlugin({
      width: 60,
      format: `  [:bar] ${chalk.green.bold(':percent')} (:elapsed seconds) - :msg`,
      clear: false,
      summary: (total, succeeded, failed, startTime, endTime, messages) => {
        const msg = `${succeeded} succeeded, ${failed} failed`;
        console.log(chalk.green.bold(`\n${total} completed in ${endTime - startTime} ms - ${msg}`));
      },
})

包含内容、进度条、进度百分比、消耗时间,进度条效果如下: image.png

10.2 编译速度分析

优化 webpack 构建速度,首先需要知道是哪些插件、哪些 loader 耗时长,方便我们针对性的优化。 通过 speed-measure-webpack-plugin插件进行构建速度分析,可以看到各个 loader、plugin 的构建时长,后续可针对耗时 loader、plugin 进行优化。

js
yarn add -D speed-measure-webpack-plugin
js
// webpack.dev.js
// 打包速度分析
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();

module.exports = smp.wrap(
  merge(common, {
    mode: 'development',
    devServer: {
      hot: true, // 热更新
      open: true,
      static: {
        // 告诉服务器从哪里提供内容。只有在你希望提供静态文件时才需要这样做。
        directory: path.resolve(__dirname, './public'),
      },
    },
  })
);

10.3 打包体积分析

使用 webpack-bundle-analyzer查看打包后生成的 bundle 体积分析,将 bundle 内容展示为一个便捷的、交互式、可缩放的树状图形式。帮助我们分析输出结果来检查模块在何处结束。

安装

js
yarn add webpack-bundle-analyzer -D

配置

js
//webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 

plugins:[
    new BundleAnalyzerPlugin()
]

运行 npm run build ,编译结束新开一个页面,默认8888端口,可以看到bundle之间的关系。 image.png

11.优化打包速度

构建速度指的是我们每次修改代码后热更新的速度以及发布前打包文件的速度。

使用最新的webpack版本, 通过webpack自身的迭代优化,来加快构建速度。 这一点是非常有效的,如 webpack5 较于 webpack4,新增了持久化缓存、改进缓存算法等优化,webpack5 新特性可查看 参考资料

11.1 缩小文件的搜索范围(配置include exclude alias extensions)

  • alias: 当我们代码中出现 import 'vue'时, webpack会采用向上递归搜索的方式去node_modules 目录下找。
    • 为了减少搜索范围我们可以直接告诉webpack去哪个路径下查找。也就是别名(alias)的配置。
    • 也能使我们编写代码更加方便
  • include exclude 同样配置include exclude也可以减少webpack loader的搜索转换时间
js
module: {
  rules: [
    {
      test: /\.vue$/,
      use: ['vue-loader'], // 解析.vue模板
      include: [path.resolve(__dirname, '../src')],
    },
    {
      test: /\.css$/,
      use: [MiniCssExtractPlugin.loader, 'css-loader'], // 从右往左解析原则
    },
    {
      test: /\.less$/,
      use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
    },
    {
      test: /(\.jsx|\.js)$/,
      use: [
        'babel-loader',
        // path.resolve(__dirname, '../loader/drop-console.js'),
      ],
      exclude: /node_modules/,
    },
  ],
},
  • extensions webpack会根据extensions定义的后缀查找文件(频率较高的文件类型优先写在前面)
    • 如果有多个文件有相同的名字,但后缀名不同,webpack 会解析列在数组首位的后缀的文件 并跳过其余的后缀
    • 但是要确保同一个目录下面没有重名的,不同后缀的文件
js
// 创建 import 或 require 的别名,来确保模块引入变得更简单
resolve: {
  // 别名
  alias: {
    '@': path.resolve(__dirname, '../src'),
  },
  // 尝试按顺序解析这些后缀名。如果有多个文件有相同的名字,
  // 但后缀名不同,webpack 会解析列在数组首位的后缀的文件 并跳过其余的后缀。
  // 但是要确保同一个目录下面没有重名的 css 或者 js 文件
  extensions: ['.js', '.json', '.vue'],
},

11.2 缓存Cache

缓存生成的 webpack 模块和 chunk,来改善构建速度,无须在使用dll、cache-loader,使用cache帮助缓存。 可以将构建过程的 webpack 模板进行缓存,大幅提升二次构建速度、打包速度,当构建突然中断,二次进行构建时,可以直接从缓存中拉取,可提速 90% 左右。

cachek: 在构建过程中会对文件进行缓存,避免每次构建都重新处理所有文件,这个缓存被称为cache.

  • type 'memory' | 'filesystem'
    • memory webpack 在内存中存储缓存,不允许额外的配置
    • filesystem 文件系统 -store
    • 当编译器空闲时,将缓存数据存放到一个文件中
js
// webpack.config.js
cache: {
  type: 'filesystem', // string: 'memory' | 'filesystem' 内存/文件系统
 cacheDirectory: path.resolve(__dirname, 'webpack_cache'), // 指定缓存目录
  maxAge: 604800000, // 缓存的最长时间(单位为毫秒)
},

image.png

11.3 多线程

可以通过多进程来实现,试想将loader 放在一个独立的 worker 池中运行,就不会阻碍其他 loader 的构建了,可以大大加快构建速度。

通过 thread-loader将耗时的 loader 放在一个独立的 worker 池中运行,加快 loader 构建速度。 happypack同样是用来设置多线程,但是在 webpack5 就不要再使用 happypack了,官方也已经不再维护了,推荐使用上文介绍的 thread-loader。

js
yarn add -D thread-loader
js
{
  test: /\.jsx|\.js$/,
  use: [
    'thread-loader',
    'babel-loader',
    path.resolve(__dirname, '../loader/drop-console.js'),
  ],
  exclude: /node_modules/,//  排除|不包含 该文件
}

由于 thread-loader 引入后,需要 0.6s 左右的时间开启新的 node 进程,本项目代码量小,可见引入 thread-loader 后,构建时间反而增加了。

因此,我们应该仅在非常耗时的 loader 前引入 thread-loader。

12.优化打包文件体积

打包的速度我们是进行了优化,但是打包后的文件体积却是十分大,造成了页面加载缓慢,浪费流量等,接下来让我们从文件体积上继续优化

12.1 externals

按照官方文档的解释,如果我们想引用一个库,但是又不想让webpack打包,并且又不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用,那就可以通过配置Externals。这个功能主要是用在创建一个库的时候用的,但是也可以在我们项目开发中充分使用 Externals的方式,我们将这些不需要打包的静态资源从构建逻辑中剔除出去,而使用 CDN 的方式,去引用它们。 有时我们希望我们通过script引入的库,如用CDN的方式引入的jquery,我们在使用时,依旧用require的方式来使用,但是却不希望webpack将它又编译进文件中。

js
externals: {
  vue: 'Vue',
  'vue-router': 'VueRouter',
},

12.2 JS压缩(Terser)

使用 TerserWebpackPlugin 来压缩 JavaScript。webpack5 自带最新的 terser-webpack-plugin,无需手动安装。 optimizatoin 优化,所有的优化都可以手动配置和重写。

  • minimizer 允许你通过提供一个或多个定制过的TerserPlugin实例,覆盖默认的压缩工具
js
// JS压缩
const TerserPlugin = require('terser-webpack-plugin');
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin({ parallel: threads })]
  }

12.3 CSS压缩

使用 CssMinimizerWebpackPlugin 压缩 CSS 文件,CssMinimizerWebpackPlugin 将在 Webpack 构建期间搜索 CSS 文件,优化、压缩 CSS。

和 optimize-css-assets-webpack-plugin 相比,css-minimizer-webpack-plugin 在 source maps 和 assets 中使用查询字符串会更加准确,而且支持缓存和并发模式下运行。

js
yarn add -D css-minimizer-webpack-plugin
js
// 引入CSS压缩
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: threads
      }),
      new CssMinimizerPlugin({
        parallel: threads
      })
    ]
  }

12.4 代码分离

代码分离能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,可以缩短页面加载时间。

抽取重复代码 SplitChunk

js
 optimization: {
    minimize: true,
    // js\css压缩
    minimizer: [
      new TerserPlugin({
       parallel: 4,
      }),
      new CssMinimizerPlugin({
         parallel: 4,
        // parallel: threads 报错 用4代替
      })
    ],
    splitChunks: {
      // include all types of chunks
      chunks: 'all',
      // 重复打包问题
      cacheGroups: {
        vendors: {
          // node_modules里的代码
          test: /[\\/]node_modules[\\/]/,
          chunks: 'all',
          // name: 'vendors', 一定不要定义固定的name
          priority: 10, // 优先级
          enforce: true,
        }
      }
    }
  }

CSS 文件分离

js
yarn add -D mini-css-extract-plugin

12.5 Tree Shaking(摇树)

摇树,顾名思义,就是将枯黄的落叶摇下来,只留下树上活的叶子。枯黄的落叶代表项目中未引用的无用代码,活的树叶代表项目中实际用到的源码。

但是要想使其生效,生成的代码必须是ES6模块, 通过 package.json 的 "sideEffects" 属性,来实现这种方式。

js
{
  "name": "your-project",
  "sideEffects": false
}
js
module.exports = {
  mode: 'development', // 我们先使用开发模式下验证
  devtool: 'cheap-module-source-map',  //使用source map的这个模式
  optimization: {
    usedExports: true,  // 表示开启使用tree shaking
  },
}

添加 TreeShaking 后,未引用的代码,将不会被打包

设置mode为production的时候已经自动开启了tree-shaking。 但是要想使其生效,生成的代码必须是ES6模块。不能使用其它类型的模块如CommonJS之流

INFO

Source Map是一种文件格式,用于映射编译后的代码到原始的源代码。当开发者在开发过程中使用了一些高级的特性,例如压缩、混淆、编译时,代码已经与源代码的结构发生了改变,因此可能会导致调试时的困难。

12.6 按需加载

通过 webpack 提供的 import() 语法 动态导入 功能进行代码分离,通过按需加载,大大提升网页加载速度。

js
Vue.use(VueRouter);

const Home = () =>
  import(/* webpackChunkName: "Home" */ '@/vue-views/home.vue');

const About = () =>
  import(/* webpackChunkName: "About" */ '@/vue-views/about.vue');

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
];
const router = new VueRouter({ routes });

export default router;