一篇搞懂webpack工作流

写在前面

我们每天都在和webpack打交道,都知道他是一个模块和和打包工具,那还有哪些替代工具呢,为什么最终选择webpack?前面也在深入理解ast编译解析原理中讲到webpack是基于ast语法树进行拓展的,那如何通过操作ast的呢?webpack的工作流是怎样的呢?下面都会展开

webpack类似的工具有哪些,为什么要选择webpack

grunt/gulp/rollup/webpack

  • grunt
介绍: 
自动化程度高,对于需要反复重复的任务:压缩、编译、单元测试、linting;它运用的是配置的思想。

缺点: 
1. 配置项太多
2. 不同的插件可能会有自己的扩展字段
3. 因此学习成本会很高,运用的时候需要明白各种插件的配置规则和配合方式
复制代码
  • gulp
1. 基于node.js的stream流打包工具
2. 定位是基于任务流的自动化打包工具
3. 通过task对整个开发过程进行构建
复制代码
  • rollup
1. rollup是ES6模块化工具,最大的亮点是利用了ES6的模块设计,利用tree-shaking生成更简洁、更简单的代码
2. 一般情况下开发类库使用rollup,开发项目使用webpack
3. 代码是基于ES6模块,希望代码直接被他人直接使用的情况使用rollup
4. 不建议使用情况:需要处理的静态资源比较多、需要代码拆分、构建的项目需要引入很多的CommonJS模块依赖时
复制代码
  • webpack
  1. 是一个模块化管理工具和打包工具。通过loader的转换,任何形式的资源都可以视为模块,比如CommonJS模块、AMD模块、ES6模块、CSS、图片等,也就是可以将松散的模块按照依赖和规则打包成符合生产环境部署的前端资源
  2. 可以将按需加载的模块进行分割,等需要的时候再异步加载
  3. webpack被定义为一个模块打包器,而gulp和grunt属于构建工具。

loder和plugin的区别

简单的区别

  • loader也就是加载器。webpack将一切文件视为模块,但是webpack原生只是能解析js文件,有了loader就能把其他所有的文件转换成js文件,那么就能通过webpack对所有的文件进行解析。也就是loader给webpack提供了加载非js文件的能力。
  • plugin也就是插件,plugin可以扩展webpack的功能。因为在webpack的运行周期中会广播许多的事件,plugin只需要监听这些事件,在合适的时候通过webpack提供的API对ast进行操作,就达到了目的。

深入分析一下webpack的构建流程

先画一个概念图进行总结

image.png

注意: 在webpack整个构建过程中,webpack会在特定时间广播出特定的事件,插件在监听到对应的事件后执行特定的逻辑,并且插件可以调用webpack提供的API改变webpack的运行结果。

模拟实现webpack构建流程

RunPlugin.js

module.exports = class RunPlugin {
    apply(compiler) {
        compiler.hooks.run.tap("RunPlugin", () => {
            console.log('RunPlugin');
        })
    }
}
复制代码

DonePlugin.js

module.export = class DonePlugin {
    apply(compiler) {
        compiler.hooks.run.tap("DonePlugin", () => {
            console.log('DonePlugin');
        })
    }
}
复制代码

flow.js

const fs = require('fs');
const path = require('path');
const { SyncHook } = require("tapable");//这个包会暴露出webpack的api
function babelLoader(source) {
    return `val sum = function sum(a, b) {
        return a + b;
    }`
}
class Compiler {
    constructor(options) {
        this.options = options;
        this.hooks = {
            run: new SyncHook(),
            done: new SyncHook()
        }
    }
    run() {
        this.hooks.run.call();
        let modules = [];
        let chunks = [];
        let files = [];
        //确定入口: 根据配置中的entry找出所有的入口文件
        let entry = path.join(this.options.context, this.options.entry);
        //从入口文件出发,调用所有配置的Loader对模块进行编译
        let entryContent = fs.readFileSync(entry, 'utf8');
        let entrySource = babelLoader(entryContent);
        let entryModule = {id: entry, source: entrySource};
        modules.push(entryModule);
        //再找出该模块依赖的模块,再递归本步骤直到所有依赖的文件都经过了本步骤的处理。
        let title = path.join(this.options.context, './src/title.js');
        let titleContent = fs.readFileSync(title, 'utf8');
        let titileSource = babelLoader(titleContent);
        let titleMoudle = {id: title, source: titleSource};
        modules.push(titleMoudle);
        //根据入口和模块之间的依赖关系,组成一个个包含多个模块的chunk
        let chunk = {name: 'main', modules};
        chunks.push(chunk);
        // 再把每一个chunk转换成一个单独的文件加入到输出列表
        let file = {
        file:this.options.output.filename,
        source: 
        `
        (function(modules) {
            function __webpack_require__(moduleId) {
                var module = {exports: {}};
                modules[moduleId].call(
                module.exports,
                module,
                module.exports,
                __webpack_require__
                );
                return module.exports;
            }
            return __webpack_require__("./src/app.js")
        })({
            "./src/app.js": function(module, exports, _webapck_require__) {
                var title = __webpack_require__("./src/title.js");
                console.log('title')
            },
            "./src/title.js": function(module) {
            module.exports = 'title';
            } ,
        })`
 
        
        }
        files.push(file);
        //在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
        let outputPath = path.join(
            this.options.output.path,this.option.output.filename
        );
        fs.writeFileSync(outputPath, file.source,'utf8');
        this.hooks.done.call();
    }
}

//1. 从配置文件和shell语句中读取并合并参数,得出最终的参数
let options = require('./webpack.config');
//2. 用上一步得到的参数初始化compiler对象
let compiler = new Compiler(options);
//3. 加载所有配置的插件
if(options.plugins && Array.isArray(options.plugins)) {
   for(const plugin of options.plugins) {
       plugin.apply(compiler);
   }
}
//4. 执行对象的run方法开始执行编译
compiler.run();
复制代码

webpack.config.js

const path = require('path');
const RunPlugin = require("./plugins/RunPlugin");
const DonePlugin = require("./plugins/RunPlugin");
module.exports = {
    context: process.pwd(),
    mode: "development",
    devtool: false,
    entry: "./src/app.js",
    output: {
        path: path.resolve(__dirname,, "dist"),
        filename: "main.js"
    },
    module: {
        rules: [
         {
            test: /\.jsx?$/,
            use: {
                loader: "babel-loader",
                options: {
                    presets: ["@babel/preset-env"]
                },
            },
            include: path.join(__dirname, "src"),
            exclude: /node_modules/,
          }
        ]
    },
    plugins: [new RunPlugin(), new DonePlugin()],
    devServer: {}
}
复制代码

顺便记录一下常见的loader和plugin以及解决的问题

  • loader
loader 功能
babel-loader 把ES6或react转成ES5
css-loader 加载css,支持模块化、压缩、文件导入等特性
eslint-loader 通过eslint检查javascript代码
file-loader 把文件输出到一个文件夹中,在代码中通过相对路径来引用
url-loader 类似file-loader,但是当文件很小的时候会把文件内容以base64方式注入到代码中
sass-loader 将sass/scss文件编译成css
postcss-loader 使用postcss处理css
css-loader 处理background:(url)、@import语法,让webpack能根据正确的路径进行模块化
style-loader 把css代码注入到javascript中,通过DOM操作区加载css
  • plugin
插件 功能
case-sensitive-paths-webpack-plugin 路径有误报错
terser-webpack-plugin 使用terser来压缩javascript
html-webpack-plugin 自动生成带有入口文件引用的index.html
webpack-manifest-plugin 生产资产的显示清单文件
optimize-css-assets-webpack-plugin 用于优化或者压缩css资源
mini-css-extract-plugin 将css提取成独立的文件,对每个包含css的js文件都会创建一个css文件,支持按需加载css和sourceMap
ModuleScopePlugin 引用了src目录外的文件进行报警
interpolateHtmlPlugin 和htmlWebpackPlugin串行使用,允许在index.html中添加变量
ModuleNotFoundPlugin 找不到模块的时候提供更详细的上下文信息
DefinePlugin 创建一个在编译时可配置的全局常量,如果你自定义了一个全局变量PRODUCTION,可在此设置其值来区分开发还是生产环境
HotModuleReplacementPlugin 启用模块热替换
WatchMissingNodeModulesPlugin 安装库后自动重新构建打包文件

sourceMap

总结:

  • 开发环境: cheap-module-eval-source-map
  • 生产环境: cheap-module-source-map

如何利用webpack优化性能?

太晚了,明天接着写

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享