Analysis of webpack packaging principle

1. Find the official compilation library all.min.js of the QR code generation plug-in, and use this file as a reference to find the method of QR code in all JS in the project, put it in this file uniformly, and then replace it?
2. Create a sample demo.html file, and put the found QR code-related js file into this html file to import calls?
100% will report an error, and then find the corresponding method based on this error report?
The problem with this method is, how to initialize the import? Never done it before.
3. Understand the compilation idea of ​​webapck compiled files, find the corresponding original method of the current method, kill all the found methods, and then import the all.min.js file, it can run smoothly, and it will be OK if there are no bugs. Potentially causing problems, in existing files, there may be legacy methods that have not been called.

Analyze webapck compilation files to understand compilation ideas

1、找到二维码生成插件的官方编译库 all.min .js,以这个文件为参考,找到项目中所有的JS中,有二维码的方法,统一放到这个文件中,再进行替换?
2、创建一个示例demo.html文件,把找到的二维码相关的js文件放到这个html文件中引入调用?
百分百会报错,然后根据这个报错去找对应的方法?
这个方法的问题在于,如何初始化引入?以前没做过。
3、了解webapck编译文件的编译思路,找到当前方法的对应的原方法,将找到的方法,全部干掉,再引入all.min.js文件,能跑得通,不会有bug就OK了。可能造成的问题,现有文件中,有可能有没有被调用的方法遗留。

分析webapck编译文件,了解编译思路
!function (modules) {
    
    
    /**
     * 函数说明
     * 整个函数声明了一个变量installedMoudules,
     * 函数webpack_require,给函数添加了属性
     * m 保存的是传入的模块数组modules
     * c installedModules变量
     * d
     * r
     * t
     * n
     * o
     * p一个空字符串
     * 
    */
   // modules就是一个数组,参数就是一个个我们声明的模块
    var installedModules = [];
    function __webpack_require__(moduleId) {
    
    
        // moduleId 顾名思义,我们声明的模块ID
        if(installedMOdules[moduleId])
            return installedMOdules[moduleId].exports;
        var module = installedModules[moduleId] = {
    
    
            i: moduleId,
            l: false,
            export: []
        };

        //调模块的函数,modules
        modules[moduleId].call(module.export, module, module.exports, __webpack_require__);
        module.l = true;

        //返回对应模块
        return module.exports;
    }

    __webpack_require__.m = modules;
    __webpack_require__.c = installedModules;
    __webpack_require__.d = function (exports, name, getter) {
    
    
        if (!__webpack_require__.o(exports, name, getter)) {
    
    
            if (!__webpack_require__.o(exports, name)) {
    
    
                Object.defineProperty(exports, name, {
    
    
                    configurable: false,
                    enumerable: true,
                    get: getter
                });
            }
        }
    };

}

personal understanding

webpack is a tool module of npm, a JS application packager, which packages each module in the application into one or more bundle files.

With the help of loaders and plugins, it can change, compress and optimize various files.

Input different resources, such as: html, css, js, img, font files, etc., and then output them as files that the browser can parse normally.

Packaging idea
1. Use babel to complete code conversion and analysis, and generate a single file dependent module Map

2. Start recursive analysis from the entrance and generate a dependency map of the entire project

3. Package each reference module into an immediate execution function

4. Write the final bundle file into bundle.js

Dependency Module Map for a single file

We use these packages:

@babel/parser: Responsible for parsing code into an abstract syntax tree

@babel/traverse: A tool for traversing the abstract syntax tree, we can parse specific nodes in the syntax tree, and then do some operations, such as ImportDeclaration to get the module introduced by import, and FunctionDeclaration to get the function

@babel/core: code conversion, such as ES6 code to ES5 mode

From the functions of these modules, it can actually be deduced how to obtain the dependent modules of a single file, and turn to Ast->traverse Ast->call ImportDeclaration. code show as below:

//exportDependencies.js
const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const babel = require('@babel/core')

const exportDependencies = (filename) => {
    
    
    const content = fs.readFileSync(filename, 'utf-8')
    //转为Ast
    const ast = parser.parse(content, {
    
    
        sourceType: 'module'//这个参数用来识别ES Module
    })

    const dependencies = {
    
    }
    //遍历AST抽象语法树
    traverse(ast, {
    
    
        //调用ImportDeclaration获取通过import引入的模块
        ImportDeclaration({
    
    node}) {
    
    
            const dirname = path.dirname(filename)
            const newFile = './' + path.join(dirname, node.source.value)
            //保存依赖模块
            dependencies[node.source.value] = newFile
        }
    })
    //使用@babel/core和@babel/preset-env包转化代码
    const {
    
    code} = babel.transformFromAst(ast, null, {
    
    
        presets: ["@babel/preset-env"]
    })
    return{
    
    
        fulename,//这个文件名
        dependencies,//该文件所依赖的模块组成的Map
        code //转换完成代码
    }
}
module.exports = exportDependencies
//forexample
//info.js
const a = 1
export a
//index.js
import info from "./info.js"
console.log(info)

//testExport.js
const exportDependencies = require('./exportDependencies')
console.log(exportDependencies('./src/index.js'))

Dependency Module Map for a single file

With the basis of obtaining the dependencies of a single file, we can further obtain the module dependency map of the entire project on this basis. First, calculate from the entry to get the entryMap, then traverse entryMap.dependencies, take out its value (that is, the path of the dependent module), and then obtain the dependency map of the dependent module, and so on recursively, the code is as follows:

const exportDependencies = require('./exportDependencies')

//entry为入口文件
const exportGraph = (entry) => {
    
    
    const entryModule = exportDependencies(entry)
    const graphArray = [entryModule]
    for(let i = 0; i < graphArray.length; i++) {
    
    
        const item = graphArray[i];
        //拿到文件所依赖的模块,dependencies的值参考exportDependencies
        const {
    
     dependencies } = item;
        for(let j in dependencies) {
    
    
            graphArray.push(
                exportDependencies(dependencies[j])
            )//关键代码,目的是将入口模块机器所有相关的模块放入数组中
        }
    }
    //接下来生成图谱
    const graph = {
    
    }
    graphArray.forEach(item => {
    
    
        graph[item.filename] = {
    
    
            dependencies: item.dependencies,
            code: item.code
        }
    })
    //可以看出graph其实是 文件路径名: 文件内容的集合
    return graph
}

module.export = exportGraph

output immediate function

First of all, when our code is loaded into the page, it needs to be executed immediately. So the output bundle.js is essentially an immediate execution function. We mainly pay attention to the following points:

When we write modules, we use import/export. After conversion, it becomes require/exports

If we want require/exports to work properly, then we have to define these two things and add them to bundle.js

In the dependency graph, the code becomes a string. To execute, you can use eval

Therefore, we have to do these jobs:

1. Define a require function. The essence of the require function is to execute the code of a module, and then mount the corresponding variable to the exports object

2. Obtain the dependency map of the entire project, starting from the entry, call the require method. The complete code is as follows:

const exportGraph = require('./exportGraph')
//写入文件,可以用fs.writeFileSync等方法,写入到output.path中
const exportBundle = require('./exportBundle')

const exportCode = (entry) => {
    
    
    //要先把对象转换为字符串,不然在下面的模板字符串中会默认调取对象的toString方法,参数变成[Object Object]
    const graph = JSON.stringify(exportGraph(entry))
    exportBundle(`
        (function(graph) {
    
    
            //require函数的本质是执行一个模块的代码,然后将相应的变量挂载到exports对象上
            function require(module) {
    
    
                //localRequire的本质是拿到依赖包的exports变量
                function localRequire(relativePath) {
    
    
                    return require(graph(graph[module].dependencies[relativePath]);)
                }
                var exports = {
    
    };
                (function(require, exports, code) {
    
    
                    eval(code);
                })(localRequire, exports, graph[module].code);
                return exports;
                //函数返回指向局部变量,形成闭包,exports变量在函数执行后不会被摧毁
            }
            require('${entry')
        })(${
    
    graph})
    )
    module.exports = exportCode
}

至此,简单打包完成。我贴一下我跑的demo的结果。bundle.js的文件内容为:

(function(graph) {
    
    
    //require函数的本质是执行一个模块的代码,然后将相应额变量挂载到exports对象上
    function require(module) {
    
    
        //localRequire的本质是拿到依赖包的exports变量
        function localRequire(relativePath) {
    
    
            returnrequire(graph[module].dependencies[relativePath]);
        }
        var exports = {
    
    };
        (function(require, exports, code) {
    
    
            eval(code);
        })(localRequire, exports, graph[module].code);
        return exports;//函数返回指向局部变量,形成闭包,exports变量在函数执行后不会被摧毁
        require('./src/index.js')
    }
})({
    
    "./src/index.js":{
    
    "dependencies":{
    
    "./info.js":"./src/info.js"},"code":"\"use}})

Guess you like

Origin blog.csdn.net/weixin_43706224/article/details/128919212