Search by
    简体中文
    主题设置

    anyspace

    webpack初体验

    2024年2月26日 • ☕️☕️ 10 min read阅读量 : •••

    写在前面

    从毕业到现在已经一年多的时间了,在日常的开发中或多或少接触过webpack,但没有系统的学习过webpack,是时候着手学习一下webpack了,对前端工程化的了解更进一步。

    什么是webpack

    本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。

    核心

    • entry
    • output
    • loader
    • plugin

    基本构建流程

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

    初始化参数:解析webpack配置参数,合并shell传入和webpack.config.js文件配置的参数,形成最后的配置结果。 开始编译:上一步得到的参数初始化compiler对象,注册所有配置的插件,插件监听webpack构建生命周期的事件节点,做出相应的反应,执行对象的 run 方法开始执行编译。 确定入口:从配置的entry入口,开始解析文件构建AST语法树,找出依赖,递归下去。 编译模块:递归中根据文件类型和loader配置,调用所有配置的loader对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。 完成模块编译:在经过第4步使⽤ Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系; 输出资源:根据⼊⼝和模块之间的依赖关系,组装成⼀个个包含多个模块的 Chunk,再把每个 Chunk 转换成⼀个单独的⽂件加⼊到输出列表,这步是可以修改输出内容的最后机会; 输出完成:在确定好输出内容后,根据配置确定输出的路径和⽂件名,把⽂件内容写⼊到⽂件系统。

    安装

    Copy
    //全局安装
    npm install -g webpack
    npm install -g webpack-cli
    
    //项目依赖
    npm install --save-dev webpack
    npm install --save-dev webpack-cli

    entry—入口

    入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。

    output—出口

    output 属性告诉 webpack 在哪里输出它所创建的 bundles。

    loader

    loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。

    plugin

    webpack插件(自动打开浏览器、热更新等)。

    手写一个plugin

    因为项目中随着时间用到的api越来越多,在后续更改维护后很难知道哪个接口是什么意思或者怎么使用,需要文档去管理,而在开发者中可能当时后端的文档是按照当时独立的事件及情况编写的,后续会很难使用。所以我有了想写一个webpack插件去根据前端api文件夹的接口函数去生成一个接口文档,便于管理和后续查阅使用。

    Copy
    const fs = require('fs');
    const path = require('path');
    
    class ApiDocPlugin {
      constructor(options) {
        this.outputPath = options.outputPath || 'api.md';
        this.apiFolder = options.apiFolder || '../api';
      }
    
      apply(compiler) {
        compiler.hooks.emit.tapAsync('ApiDocPlugin', (compilation, callback) => {
          const apiFolder = path.resolve(__dirname, apiFolder);
    
          // 遍历 API 文件夹
          fs.readdir(apiFolder, (err, files) => {
            if (err) {
              console.error('Error reading API folder:', err);
              return;
            }
    
            let apiDocContent = '# API Documentation\n\n';
    
            // 遍历每个 API 文件
            files.forEach(file => {
              const filePath = path.resolve(apiFolder, file);
    
              // 读取文件内容
              const content = fs.readFileSync(filePath, 'utf8');
    
              // 提取接口定义及其注释
              const regex = /\/\*\*([\s\S]*?)\*\/[\s\S]*?function\s+(\w+)\(/g;
              let match;
              while ((match = regex.exec(content)) !== null) {
                const comment = match[1].trim();
                const functionName = match[2];
                apiDocContent += `## ${functionName}\n\n`;
                apiDocContent += `${comment}\n\n`;
              }
            });
    
            // 将生成的 API 文档写入文件
            const outputPath = path.resolve(compilation.options.output.path, this.outputPath);
            fs.writeFileSync(outputPath, apiDocContent);
    
            console.log('API documentation generated:', outputPath);
    
            callback();
          });
        });
      }
    }
    
    module.exports = ApiDocPlugin;

    该插件会根据api文件夹下的文件中的接口函数注释生成接口文档,前端开发人员只需要按规则去写注释和接口代码即可,后续使用该插件可以直接生成md文档。

    webpack实际使用

    配置文件

    虽然webpack5可以不配置任何东西使用,但是实际项目中还是会使用配置文件。

    根目录下新建配置文件 webpack.config.js

    Copy
    const path = require('path')
    
    module.exports = {
      mode: 'development', // 模式
      entry: './src/index.js', // 打包入口地址
      output: {
        filename: 'bundle.js', // 输出文件名
        path: path.join(__dirname, 'dist') // 输出文件目录
      }
    }

    mode

    供 mode 配置选项,告知 webpack 使用相应模式的内置优化,默认值为 production,另外还有 development、none,上一步也已经配置了mode。

    • development 开发模式,打包更加快速,省了代码优化步骤
    • production 生产模式,打包比较慢,会开启 tree-shaking 和 压缩代码
    • none 不使用任何默认优化选项

    配置loader

    因为webpack默认只能处理js、json文件,我们实际开发中会有css、less等等类型的文件,这样我们就必须使用loader来处理不同类型的文件了。

    css-loader

    安装css-loader来处理css文件

    Copy
    const path = require('path')
    
    module.exports = {
      mode: 'development', // 模式
      entry: './src/main.css', // 打包入口地址
      output: {
        filename: 'bundle.css', // 输出文件名
        path: path.join(__dirname, 'dist') // 输出文件目录
      },
      module: {     rules: [ // 转换规则      {        test: /\.css$/, //匹配所有的 css 文件        use: 'css-loader' // use: 对应的 Loader 名称      }    ]  }}
    

    其实loader就是将webpack不认识的内容变成认识的内容

    配置plugin

    插件(Plugin)可以贯穿 Webpack 打包的生命周期,执行不同的任务

    html-webpack-plugin

    例如:如果我们想要将打包后的css和js自动引入到index.html中,我们可以使用 html-webpack-plugin插件。

    Copy
    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      mode: 'development', // 模式
      entry: './src/index.js', // 打包入口地址
      output: {
        filename: 'bundle.js', // 输出文件名
        path: path.join(__dirname, 'dist') // 输出文件目录
      },
      module: { 
        rules: [
          {
            test: /\.css$/, //匹配所有的 css 文件
            use: 'css-loader' // use: 对应的 Loader 名称
          }
        ]
      },
      plugins:[ // 配置插件    new HtmlWebpackPlugin({      template: './src/index.html'    })  ]}

    这样打包后dist中的index.html文件中就会自动使用script和link标签引入打包好的js和css资源。

    clean-webpack-plugin

    为了打包目录的纯净,我们每次打包都要手动删除之前的dist文件夹,我们可以使用插件clean-webpack-plugin让webpack打包时自动先清楚旧的dist

    Copy
    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    // 引入插件
    const { CleanWebpackPlugin } = require('clean-webpack-plugin')
    
    module.exports = {
      // ...
      plugins:[ // 配置插件
        new HtmlWebpackPlugin({
          template: './src/index.html'
        }),
        new CleanWebpackPlugin() // 引入插件  ]
    }

    区分环境

    一般来说,开发环境和生产环境有不同的需求。

    本地环境:

    • 需要更快的构建速度
    • 需要打印 debug 信息
    • 需要 live reload 或 hot reload 功能
    • 需要 sourcemap 方便定位问题

    生产环境:

    • 需要更小的包体积,代码压缩+tree-shaking
    • 需要进行代码分割
    • 需要压缩图片体积

    cross-env区分环境

    Copy
    npm install cross-env -D

    配置启动命令

    在pakage.json中:

    Copy
    "scripts": {
        "dev": "cross-env NODE_ENV=dev webpack serve --mode development", 
        "test": "cross-env NODE_ENV=test webpack --mode production",
        "build": "cross-env NODE_ENV=prod webpack --mode production"
      },

    在webpack配置文件中:

    Copy
    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    console.log('process.env.NODE_ENV=', process.env.NODE_ENV) // 打印环境变量
    
    const config = {
      entry: './src/index.js', // 打包入口地址
      output: {
        filename: 'bundle.js', // 输出文件名
        path: path.join(__dirname, 'dist') // 输出文件目录
      },
      module: { 
        rules: [
          {
            test: /\.css$/, //匹配所有的 css 文件
            use: 'css-loader' // use: 对应的 Loader 名称
          }
        ]
      },
      plugins:[ // 配置插件
        new HtmlWebpackPlugin({
          template: './src/index.html'
        })
      ]
    }
    
    module.exports = (env, argv) => {
      console.log('argv.mode=',argv.mode) // 打印 mode(模式) 值
      // 这里可以通过不同的模式修改 config 配置
      return config;
    }
    

    这样执行不同的命令,我们可以不同的值区分当前的环境。

    启动dev-server

    安装dev-server

    Copy
    npm intall webpack-dev-server

    配置本地服务

    Copy
    // webpack.config.js
    const config = {
      // ...
      devServer: {    contentBase: path.resolve(__dirname, 'public'), // 静态文件目录    compress: true, //是否启动压缩 gzip    port: 8080, // 端口号    // open:true  // 是否自动打开浏览器  }, // ...
    }
    module.exports = (env, argv) => {
      console.log('argv.mode=',argv.mode) // 打印 mode(模式) 值
      // 这里可以通过不同的模式修改 config 配置
      return config;
    }
    

    contentBase:webpack 在进行打包的时候,对静态文件的处理,例如图片,都是直接 copy 到 dist 目录下面。但是对于本地开发来说,这个过程太费时,也没有必要,所以在设置 contentBase 之后,就直接到对应的静态目录下面去读取文件,而不需对文件做任何移动,节省了时间和性能开销。

    启动本地服务

    Copy
    npm run dev

    引入css

    使用css-loader处理css文件,但是还不不够,需要将处理好的css的加载到页面上,可以使用style-loader将处理好的css通过style标签的形式添加到页面上。

    安装style-loader

    Copy
    npm install style-loader -D

    配置loader

    Copy
    const config = {
      // ...
      module: { 
        rules: [
          {
            test: /\.css$/, //匹配所有的 css 文件
            use: ['style-loader','css-loader']
          }
        ]
      },
      // ...
    }

    Loader 的执行顺序是固定从后往前,即按 css-loader —> style-loader 的顺序执行

    style-loader的核心逻辑

    Copy
    const content = `${样式内容}`
    const style = document.createElement('style');
    style.innerHTML = content;
    document.head.appendChild(style);

    css兼容性

    使用postcss-loader,自动添加css3部分属性的浏览器前缀。

    引入Less或Sass

    安装对应的less-loader或sass-loader

    分离样式文件

    style-loader是将处理好的css样式通过style标签的形式添加到页面上,一般来说我们想要通过css文件形式引入到页面上。

    安装mini-css-extract-plugin

    Copy
    npm install mini-css-extract-plugin -D

    修改配置

    Copy
    // ...
    // 引入插件
    const MiniCssExtractPlugin = require('mini-css-extract-plugin')
    
    
    const config = {
      // ...
      module: { 
        rules: [
          // ...
          {
            test: /\.(s[ac]|c)ss$/i, //匹配所有的 sass/scss/css 文件
            use: [
              // 'style-loader',
              MiniCssExtractPlugin.loader, // 添加 loader
              'css-loader',
              'postcss-loader',
              'sass-loader', 
            ] 
          },
        ]
      },
      // ...
      plugins:[ // 配置插件
        // ...
        new MiniCssExtractPlugin({ // 添加插件
          filename: '[name].[hash:8].css'
        }),
        // ...
      ]
    }
    
    // ...

    图片和字体文件

    开发环境下虽然可以通过contentBase直接读取静态文件,但是生产环境代码中引入图片会报错。需要在打包时做处理。


    Kou ShiXiang

    一个记录知识和生活的神秘小空间