1、找到二维码生成插件的官方编译库 all.min .js,以这个文件为参考,找到项目中所有的JS中,有二维码的方法,统一放到这个文件中,再进行替换?
2、创建一个示例demo.html文件,把找到的二维码相关的js文件放到这个html文件中引入调用?
百分百会报错,然后根据这个报错去找对应的方法?
这个方法的问题在于,如何初始化引入?以前没做过。
3、了解webapck编译文件的编译思路,找到当前方法的对应的原方法,将找到的方法,全部干掉,再引入all.min.js文件,能跑得通,不会有bug就OK了。可能造成的问题,现有文件中,有可能有没有被调用的方法遗留。
分析webapck编译文件,了解编译思路
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
});
}
}
};
}
个人理解
webpack是是npm的工具模块,是一个JS应用打包器, 它将应用中的各个模块打包成一个或者多个bundle文件。
借助loaders和plugins,它可以改变、压缩和优化各种各样的文件。
输入不同资源,比如:html、css、js、img、font文件等,然后将它们输出浏览器可以正常解析的文件。
打包思路
1、利用babel完成代码转换及解析,并生成单个文件的依赖模块Map
2、从入口开始递归分析,并生成整个项目的依赖图谱
3、将各个引用模块打包为一个立即执行函数
4、将最终的bundle文件写入bundle.js中
单个文件的依赖模块Map
我们使用这几个包:
@babel/parser:负责将代码解析为抽象语法树
@babel/traverse:遍历抽象语法树的工具,我们可以在语法树中解析特定的节点,然后做一些操作,如ImportDeclaration获取通过import引入的模块,FunctionDeclaration获取函数
@babel/core:代码转换,如ES6的代码转为ES5的模式
由这几个模块的作用,其实已经可以推断出应该怎样获取单个文件的依赖模块了,转为Ast->遍历Ast->调用ImportDeclaration。代码如下:
//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'))
单个文件的依赖模块Map
有了获取单个文件依赖的基础,我们就可以在这基础上,进一步得出整个项目的模块依赖图谱了。首先,从入口开始计算,得到entryMap,然后遍历entryMap.dependencies,取出其value(即依赖的模块的路径),然后再获取这个依赖模块的依赖图谱,以此类推递归下去即可,代码如下:
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
输出立即执行函数
首先,我们的代码被加载到页面中的时候,是需要立即执行的。所以输出的bundle.js实质上要是一个立即执行函数。我们主要注意以下几点:
我们写模块的时候,用的是 import/export.经转换后,变成了require/exports
我们要让require/exports能正常运行,那么我们得定义这两个东西,并加到bundle.js里
在依赖图谱里,代码都成了字符串。要执行,可以使用eval
因此,我们要做这些工作:
1、定义一个require函数,require函数的本质是执行一个模块的代码,然后将相应变量挂载到exports对象上
2、获取整个项目的依赖图谱,从入口开始,调用require方法。完整代码如下:
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}})