webpack原理篇(五十八):实战开发一个简易的webpack

简介: webpack原理篇(五十八):实战开发一个简易的webpack

说明

玩转 webpack 学习笔记



模块化:增强代码可读性和维护性

  1. 传统的网页开发转变成 Web Apps 开发
  2. 代码复杂度在逐步增高
  3. 部署时希望把代码优化成几个 HTTP 请求
  4. 分离的 JS文件/模块,便于后续代码的维护性



常见的几种模块化方式


ES module:

import * as largeNumber from 'large-number';
// ...
largeNumber.add('999', '1');

CJS:

const largeNumbers = require('large-number');
// ...
largeNumber.add('999', '1');

AMD:

require(['large-number'], function (large-number) {
  // ...
  largeNumber.add('999', '1');
});




AST 基础知识


抽象语法树(abstract syntax tree 或者缩写为 AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。树上的每个节点都表示源代码中的一种结构。

在线:demo: https://esprima.org/demo/parse.html

39cc02c41f654a689aff9283cba9aded.png

b4ec78125f91475f874826bdad41bcaa.png




webpack 的模块机制


  • 打包出来的是一个 IIFE (匿名闭包)
  • modules 是一个数组,每一项是一个模块初始化函数
  • __webpack_require 用来加载模块,返回 module.exports
  • 通过 WEBPACK_REQUIRE_METHOD(0) 启动程序


fd0e0f7bf2a245bdb1d2ab0e7d945881.png




实现一个简易的 webpack


可以将 ES6 语法转换成 ES5 的语法,生成的 JS 文件可以在浏览器中运行

  • 通过 babylon 生成AST
  • 通过 babel-core 将AST重新生成源码

可以分析模块之间的依赖关系

  • 通过 babel-traverse 的 ImportDeclaration 方法获取依赖属性



1、新建初始化项目

新建 mini-webpack 文件夹,执行下面命令,初始化项目

npm init -y

7f5bb58386d546c9aa2a827524195910.png




2、安装相关依赖


babylon:使用 babylon 生成AST

babel-core:使用 babel-core 将AST重新生成源码

babel-traverse:使用 babel-traverse 的 ImportDeclaration 方法获取依赖属性

babel-preset-env:通过根据目标浏览器或运行时环境自动确定所需的 Babel 插件和 polyfill,将 ES2015+ 编译为 ES5 的 Babel 预设。



npm i babylon babel-core babel-traverse

9424519a279b4cc4ab4e2179ccb0b464.png

这里需要安装下面这个插件,不安装到时会报错


0cdcaa6fa8864c609678d0433703c122.png


npm i babel-preset-env

c1e59d638bf64934b47f8386dd6b9356.png



3、添加 minipack.config.js 配置文件

里面模仿 webpack 的配置

const path = require('path');
module.exports = {
    // 入口
    entry: path.join(__dirname, './src/index.js'),
    // 输出文件
    output: {
        path: path.join(__dirname, './dist'),
        filename: 'kaimo.js'
    }
}



4、添加 src 入口文件

新建 src,里面添加 index.js 文件,里面依赖 common 文件夹里的 kaimo666.js 里的方法

index.js 文件

import { hello } from './common/kaimo666.js';
document.write(hello('kaimo666'));


kaimo666.js 文件

export function hello(name) {
    return `hello ${name}`;
}



结构如下:

66c5331c1cdc4442a837ffc88f545b9e.png


5、实现 mini-webpack 的核心功能

新建 lib 文件夹,首先添加 index.js 文件,到时执行 node ./lib/index.js 就可以进行编译打包了。

// 编译模块
const Compiler = require('./compiler.js');
// 获取配置
const options = require('../minipack.config.js');
// 实例化 compiler
new Compiler(options).run();


然后实现 compiler.js 功能里面需要结束 config 的配置,以及 run 去执行。

const { getAst, getDependencis, transform } = require("./parser.js");
const path = require('path');
const fs = require('fs');
module.exports = class Compiler {
    constructor(options) {
        const { entry, output } = options;
        this.entry = entry;
        this.output = output;
        this.modules = [];
    }
    run() {
        // 从入口文件开始构建
        const entryModule = this.buildModule(this.entry, true);
        this.modules.push(entryModule);
        // 遍历模块依赖进行构建
        this.modules.map(_module => {
            _module.dependencies.map(dependency => {
                this.modules.push(this.buildModule(dependency));
            })
        })
        // 构建完成输出文件
        this.emitFiles();
    }
    /**
     * 构建模块:用于获取文件的路径,ast,相关依赖
     * @param filename 文件路径
     * @param isEntry 是否是入口文件
     * */ 
    buildModule(filename, isEntry) {
        let ast;
        if(isEntry) {
            ast = getAst(filename);
        } else {
            // 获取文件的绝对路径:process.cwd()是指当前node命令执行时所在的文件夹目录
            let absolutePath = path.join(process.cwd(), './src', filename);
            ast = getAst(absolutePath);
        }
        return {
            filename,
            dependencies: getDependencis(ast),
            transformCode: transform(ast)
        }
    }
    // 输出文件
    emitFiles() {
        // 输出的文件路径
        const outputPath = path.join(this.output.path, this.output.filename);
        // 组装依赖的 modules
        let modules = '';
        this.modules.map(_module => {
            modules += `'${_module.filename}': function (require, module, exports) { ${_module.transformCode} },`
        })
        // 组装生成的代码 bundle
        const bundle = `
            (function(modules){
                function require(fileName) {
                    const fn = modules[fileName];
                    const module = { exports: {} };
                    fn(require, module, module.exports);
                    return module.exports;
                }
                require('${this.entry}');
            })({${modules}})
        `;
        console.log("emitFiles--->", outputPath, bundle)
        // recursive: true 参数,不管创建的目录是否存在
        fs.mkdir(this.output.path, { recursive: true }, function(err) {
            if (err) throw err;
            console.log("目录创建成功");
            // 使用 fs.writeFileSync 将数据同步写入文件
            fs.writeFileSync(outputPath, bundle, 'utf-8');
            console.log("打包完毕");
        });
    }
}

最后实现 parser 里的相关方法

const fs = require('fs');
const babylon = require('babylon');
const { default: traverse } = require('babel-traverse');
const { transformFromAst } = require('babel-core');
module.exports = {
    // 获取文件的 ast
    getAst: path => {
        // 同步读取文件
        console.log("getAst----path>", path)
        const content = fs.readFileSync(path, 'utf-8');
        console.log("getAst---->", content)
        // 分析AST,从中得到 import 的模块信息(路径)
        return babylon.parse(content, {
            sourceType: 'module'
        })
    },
    // 获取文件的依赖
    getDependencis: ast => {
        const dependencies = [];
        traverse(ast, {
            // ImportDeclaration 方法:当遍历到 import 时的一个回调
            ImportDeclaration: ({ node }) => {
                // 将依赖 push 到 dependencies 中
                dependencies.push(node.source.value);
            }
        });
        return dependencies;
    },
    transform: ast => {
        // es6 转化为 es5
        const { code } = transformFromAst(ast, null, {
            presets: ['env']
        });
        return code;
    }
}


结构如下:


d49e4e1c09f94299839265865821aa31.png



6、添加脚本进行打包

在 package.json 里添加下面脚本

"build": "node ./lib/index.js"


32963a15b02a4a43acd910cdbb8f2df9.png



然后我们执行

npm run build

e8832c8d2e514234a341c4aa4fcfc775.png


打包完成之后我们可以看到多了一个 dist 的文件夹,里面有打包好的 kaimo.js 文件

744040ed584447398345096c1d845836.png



7、测试打包好的文件能否正常运行

我们在 dist 文件夹下面添加 index.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>Document</title>
</head>
<body>
    <script src="./kaimo.js"></script>
</body>
</html>


浏览器访问 index.html 文件,效果如下

828f99fb63e1466fad2726bf11686246.png


我们改动一下 src 下 index.js 的代码

import { hello } from './common/kaimo666.js';
document.write(hello('凯小默 kaimo777'));


然后打包,成功之后刷新页面,我们可以看到效果也变了。


17f672b3e4784b43baf8b881488c3869.png



目录
相关文章
|
JavaScript 前端开发 API
webpack核心原理-2
webpack核心原理-2
77 0
|
18天前
|
监控 前端开发 JavaScript
Webpack 中 HMR 插件的工作原理
【10月更文挑战第23天】可以进一步深入探讨 HMR 工作原理的具体细节、不同场景下的应用案例,以及与其他相关技术的结合应用等方面的内容。通过全面、系统地了解 HMR 插件的工作原理,能够更好地利用这一功能,为项目的成功开发提供有力保障。同时,要不断关注技术的发展动态,以便及时掌握最新的 HMR 技术和最佳实践。
|
18天前
|
缓存 前端开发 JavaScript
Webpack 动态加载的原理
【10月更文挑战第23天】Webpack 动态加载通过巧妙的机制和策略,实现了模块的按需加载和高效运行,提升了应用程序的性能和用户体验。同时,它也为前端开发提供了更大的灵活性和可扩展性,适应了不断变化的业务需求和技术发展。
|
18天前
|
缓存 前端开发 JavaScript
webpack 原理
【10月更文挑战第23天】Webpack 原理是一个复杂但又非常重要的体系。它通过模块解析、依赖管理、加载器和插件的协作,实现了对各种模块的高效打包和处理,为现代前端项目的开发和部署提供了强大的支持。同时,通过代码分割、按需加载、热模块替换等功能,提升了应用程序的性能和用户体验。随着前端技术的不断发展,Webpack 也在不断演进和完善,以适应不断变化的需求和挑战。
|
1月前
|
缓存 前端开发 JavaScript
Webpack 打包的基本原理
【10月更文挑战第5天】
|
2月前
|
JavaScript 前端开发
手写一个简易bundler打包工具带你了解Webpack原理
该文章通过手写一个简易的打包工具bundler,帮助读者理解Webpack的工作原理,包括模块解析、依赖关系构建、转换源代码以及生成最终输出文件的整个流程。
|
3月前
|
缓存 前端开发 JavaScript
Webpack 模块解析:打包原理、构造形式、扣代码补参数和全局导出
Webpack 模块解析:打包原理、构造形式、扣代码补参数和全局导出
133 1
|
6月前
|
API 开发工具 开发者
webpack热更新原理
Webpack的Hot Module Replacement(HMR)提升开发效率,无需刷新页面即可更新模块。开启HMR需在配置中设`devServer.hot: true`。Webpack构建时插入HMR Runtime,通过WebSocket监听并处理文件变化。当模块改变,Webpack发送更新到浏览器,HMR Runtime找到对应模块进行热替换,保持应用状态。开发者可利用`module.hot` API处理热替换逻辑。
|
6月前
|
前端开发 测试技术 开发者
深入理解 Webpack 热更新原理:提升开发效率的关键
深入理解 Webpack 热更新原理:提升开发效率的关键
|
6月前
|
缓存 前端开发 算法
Webpack 进阶:深入理解其工作原理与优化策略
Webpack 进阶:深入理解其工作原理与优化策略
170 2