01 转换代码
// 首先下载这些包
// 但是这些包可能下载的时候名字有变化,已经更新了,最新版才是ts,去npm搜下吧
// 1. 引入
import { parse } from '@babel/parser'
import traverse from '@babel/traverse'
import generator from '@babel/generator'
// 2. 我们写一句代码
let code = `let a = 'let';let b = '33'`
// 3. 转为ast语法树
const ast = parse(code, {sourceType: 'module'})
// 4. 遍历ast语法树
traverse(ast, {
// item 是遍历的每一项
enter: item => {
// 找到遍历的每一项中的VariableDeclaration这个类型
if (item.node.type === 'VariableDeclaration') {
// 找到item中的kind,将let改为var
if (item.node.kind === 'let') {
item.node.kind = 'var'
}
}
}
});
// 5. 将语法树转换回去
let code2 = generator(ast, {}, code)
console.log(code2)
02 找出依赖文件 解决嵌套依赖 解决循环依赖
perject_02 和 02ts对应
import { parse } from "@babel/parser";
import traverse from '@babel/traverse'
import { readFileSync } from 'fs'
// resolve 拼接路径 /Users/didi/Desktop/test/webpack-code/project_2/index.js
// relative 得到最后 index.js
// dirname 得到前面的路径 /Users/didi/Desktop/test/webpack-code/project_2
import { resolve, relative, dirname } from 'path';
// 当前目录
const projectRoot = resolve(__dirname, 'project_2')
// 类型声明
/**
* key 代表文件
* deps 存储文件中依赖文件
* code 存储代码
*/
type DepRelation = {
[key: string]: {
deps: string[],
code: string
}
}
// 初始化手机依赖的数组
const DepRelation: DepRelation = {}
// 将入口文件传入到函数中
collectionCodeAndDeps(resolve(projectRoot, 'index.js'))
console.log("~ DepRelation", DepRelation)
function collectionCodeAndDeps(failPath: string) {
// 文件的项目
const key = getProjectPath(failPath)
/**
* 解决循环依赖(DepRelation的key是所有index(索引)到的文件)
*/
if (Object.keys(DepRelation).includes(key)) {
return
}
// 获取文件内容
const code = readFileSync(failPath).toString()
// 初始化 depRealation[key]
DepRelation[key] = {deps: [], code: code}
// 将代码转为Ast
const ast = parse(code, {sourceType: 'module'})
// 分析遍历
traverse(ast, {
enter: item => {
if (item.node.type === 'ImportDeclaration') {
// item.node.source.value 是一个相对路径 如: ./a.js
const depAbsoutePath = resolve(dirname(failPath), item.node.source.value)
// 然后转译为项目路径
const depProjectPath = getProjectPath(depAbsoutePath)
// 将依赖装入DepRelation
DepRelation[key].deps.push(depProjectPath)
/**
* 解决嵌套依赖
*/
collectionCodeAndDeps(depAbsoutePath)
}
}
})
}
function getProjectPath(path: string) {
return relative(projectRoot, path).replace(/\\/g, '/')
}
03 转译代码(平稳的兼容策略(解决办法): 转译代码,将所有文件打包为一个文件)
/**
* 因为浏览器不能识别 import export
* 所以我们进行打包 转换为 require
*/
import * as babel from '@babel/core';
import { parse } from "@babel/parser";
import traverse from '@babel/traverse'
import { readFileSync } from 'fs'
// resolve 拼接路径 /Users/didi/Desktop/test/webpack-code/project_2/index.js
// relative 得到最后 index.js
// dirname 得到前面的路径 /Users/didi/Desktop/test/webpack-code/project_2
import { resolve, relative, dirname } from 'path';
// 当前目录
const projectRoot = resolve(__dirname, 'project_2')
// 类型声明
/**
* key 代表文件
* deps 存储文件中依赖文件
* code 存储代码
*/
type DepRelation = {
[key: string]: {
deps: string[],
code: string
}
}
// 初始化手机依赖的数组
const DepRelation: DepRelation = {}
// 将入口文件传入到函数中
collectionCodeAndDeps(resolve(projectRoot, 'index.js'))
console.log("~ DepRelation", DepRelation)
function collectionCodeAndDeps(failPath: string) {
// 文件的项目
const key = getProjectPath(failPath)
/**
* 解决循环依赖(DepRelation的key是所有index(索引)到的文件)
*/
if (Object.keys(DepRelation).includes(key)) {
return
}
// 获取文件内容
const code = readFileSync(failPath).toString()
// 转换为es5(本次改动点)
const { code: es5Code }: any = babel.transform(code, {
presets: ['@babel/preset-env']
})
// 初始化 depRealation[key]
DepRelation[key] = {deps: [], code: es5Code as string}
// 将代码转为Ast
const ast = parse(code, {sourceType: 'module'})
// 分析遍历
traverse(ast, {
enter: item => {
if (item.node.type === 'ImportDeclaration') {
// item.node.source.value 是一个相对路径 如: ./a.js
const depAbsoutePath = resolve(dirname(failPath), item.node.source.value)
// 然后转译为项目路径
const depProjectPath = getProjectPath(depAbsoutePath)
// 将依赖装入DepRelation
DepRelation[key].deps.push(depProjectPath)
/**
* 解决嵌套依赖
*/
collectionCodeAndDeps(depAbsoutePath)
}
}
})
}
function getProjectPath(path: string) {
return relative(projectRoot, path).replace(/\\/g, '/')
}
04 (打包器)将所有文件打包为一个文件(平稳的兼容策略(解决办法): 转译代码,将所有文件打包为一个文件)
- 这里的文件将会打包为一个文件, 然后去执行他
/**
* 本次解决问题:
* 1. 打包为dist文件, dist文件就是将所有文件包含在内,
* 2. 不在需要像index.js需要依赖其他文件, 而在dist文件中式全部包含所有文件的,
* 3. 然后执行所有模块, 所有文件放到一个文件,再执行, 这个就是打包bundel
* 运行 ts-node code/04.bundle.ts
*/
import * as babel from '@babel/core';
import { parse } from "@babel/parser";
import traverse from '@babel/traverse'
import { readFileSync } from 'fs'
// resolve 拼接路径 /Users/didi/Desktop/test/webpack-code/project_2/index.js
// relative 得到最后 index.js
// dirname 得到前面的路径 /Users/didi/Desktop/test/webpack-code/project_2
import { resolve, relative, dirname } from 'path';
// 当前目录
const projectRoot = resolve(__dirname, 'project_3')
// 类型声明
/**
* key 代表文件
* deps 存储文件中依赖文件
* code 存储代码
*/
type DepRelation = {
key: string,
deps: string[],
code: string
}[]
type Item = {
key: string,
deps: string[],
code: string
}
// 初始化手机依赖的数组
const depRelation: DepRelation = []
// 将入口文件传入到函数中
collectionCodeAndDeps(resolve(projectRoot, 'index.js'))
console.log("~ DepRelation", depRelation)
function collectionCodeAndDeps(failPath: string) {
// 文件的项目
const key = getProjectPath(failPath)
/**
* 解决循环依赖(DepRelation的key是所有index(索引)到的文件)
*/
if (depRelation.find(i => i.key === key)) {
return
}
// 获取文件内容
const code = readFileSync(failPath).toString()
// 转换为es5(本次改动点)
const { code: es5Code }: any = babel.transform(code, {
presets: ['@babel/preset-env']
})
// 初始化 depRealation[key]
const item: Item = {key, deps: [], code: es5Code}
depRelation.push(item)
// 将代码转为Ast
const ast = parse(code, {sourceType: 'module'})
// 分析遍历
traverse(ast, {
enter: path => {
if (path.node.type === 'ImportDeclaration') {
// item.node.source.value 是一个相对路径 如: ./a.js
const depAbsoutePath = resolve(dirname(failPath), path.node.source.value)
// 然后转译为项目路径
const depProjectPath = getProjectPath(depAbsoutePath)
// 将依赖装入DepRelation
item.deps.push(depProjectPath)
/**
* 解决嵌套依赖
*/
collectionCodeAndDeps(depAbsoutePath)
}
}
})
}
function getProjectPath(path: string) {
return relative(projectRoot, path).replace(/\\/g, '/')
}
05 手写一个dist文件 (dist文件就是包含所有打包后的代码,里面的code是函数然后执行函数得到结果
/**
* 这个文件是模仿dist打包文件(dist文件就是将project_3的index.js去转化降级打包为一个文件)
* depRelation是babel转译后的代码
* https://www.dvy.com.cn/2020/12/06/6976.html 这个博客有babel的转译后代码的讲解
*/
var depRelation =[
{
key: 'index.js',
deps: ['a.js', 'b.js'],
code: function(require, module, exports) {
"use strict";
// 这里就是bable转译的代码,inport转化为require _interopRequireDefault是为了做转化
var _a = _interopRequireDefault(require("./a.js"))
var _b = _interopRequireDefault(require("./b.js"))
// _interopRequireDefault的作用就是判断require的模块是否是已经被babel编译过的模块,如果是,则当前require的引用一定存在一个default属性;否则为他加一个default属性,这样便不会调用模块的default为undefined的情况了
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj}}
// _a["default"]就是调用a文件默认导出default
console.log(_a["default"].getB());
console.log(_b["default"].getA());
}
},
{
key: 'a.js',
deps: ['b.js'],
code: function(require, module, exports) {
"use strict";
// 这里就是bable转译的代码,inport转化为require _interopRequireDefault是为了做转化
Object.defineProperty(exports, "__esModule", {
value: true
})
exports["default"] = void 0;
var _b = _interopRequireDefault(require("./b.js"))
// _interopRequireDefault的作用就是判断require的模块是否是已经被babel编译过的模块,如果是,则当前require的引用一定存在一个default属性;否则为他加一个default属性,这样便不会调用模块的default为undefined的情况了
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj}}
var a = {
value: 'a',
getB: function () {
return _b["default"].value + 'from a.js'
}
}
var _default = a
exports['default'] = _default
}
},
{
key: 'b.js',
deps: ['a.js'],
code: function(require, module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
})
exports["default"] = void 0;
// 这里就是bable转译的代码,inport转化为require _interopRequireDefault是为了做转化
var _a = _interopRequireDefault(require("./a.js"))
// _interopRequireDefault的作用就是判断require的模块是否是已经被babel编译过的模块,如果是,则当前require的引用一定存在一个default属性;否则为他加一个default属性,这样便不会调用模块的default为undefined的情况了
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj}}
var b = {
value: 'b',
getA: function () {
return _a["default"].value + 'from b.js'
}
}
var _default = b
exports['default'] = _default
}
}
]
var moudles = {}
execute(depRelation[0].key)
function execute(key) {
// 缓存 如果已经 require 过,直接返回上次的结果
if (moudles[key]) { return moudles[key]}
// 找到要执行的项目
var item = depRelation.find(i => i.key === key)
// 找不到就报错,中断执行
if (!item) { throw new Error(`${item} is not found`) }
// 把相对路径变为项目路径
var pathToley = (path) => {
var dirname = key.substring(0, key.lastIndexOf('/') + 1)
var projectPath = (dirname + path).replace(/\.\//g, '').replace(/\/\//, '/')
return projectPath
}
// require 函数
var require = (path) => {
return execute(pathToley(path))
}
// 初始化当前模块
moudles[key] = { __esModule: true }
var moudle = { exports: moudles[key]}
// 初始化 moudel 方便 code 在 moudel.exports 导出属性
// 第二个参数moudel ,大部分无用,用于兼容旧代码
item.code(require, moudle, moudle.exports)
// 返回当前模块
return moudles[key]
}
07 这个文件作用是生成dist文件,加上04文件的打包器,就可以输出自动输出dist
- 那么这里发现只可以解决打包js文件,css呢? 那么就是去将css转化为js,那么就可以加载了
- 我们建立了一个loader的文件夹,实现了css-loader,就是将css,变为js引入
/**
* 运行 ts-node code/06.bundleFile.ts
*/
import { writeFileSync, writevSync } from "fs"
import { resolve, relative, dirname, join } from 'path';
import { mkdir } from 'shelljs'
import * as babel from '@babel/core';
import { parse } from "@babel/parser";
import traverse from '@babel/traverse'
import { readFileSync } from 'fs'
const projectName = 'project_3'
const projectRoot = resolve(__dirname, projectName)
type DepRelation = {
key: string,
deps: string[],
code: string
}[]
type Item = {
key: string,
deps: string[],
code: string
}
// 初始化手机依赖的数组
// 类型声明
// 将入口文件的绝对路径传入函数,如 D://faiel/index.js
const depRelation: DepRelation = []
collectionCodeAndDeps(resolve(projectRoot, 'index.js'))
console.log("~ depRelation", depRelation)
const dir = `${projectName}/dist`
mkdir('-p', dir)
writeFileSync(join(dir, 'dist.js'), generatorCode())
function generatorCode() {
let code = ''
code += 'var depRelation = [' + depRelation.map(item => {
const { key,deps, code } = item
return `{
key: ${JSON.stringify(key)},
deps: ${JSON.stringify(deps)},
code: function(require, moudle, exports) {
${code}
}
}`
}).join(',') + '];\n'
code += 'var moudles = {}\n'
code += 'execute(depRelation[0].key)\n'
code += `
function execute(key) {
if (moudles[key]) { return moudles[key]}
var item = depRelation.find(i => i.key === key)
if (!item) { throw new Error(\`\${item} is not found\`) }
var pathToley = (path) => {
var dirname = key.substring(0, key.lastIndexOf('/') + 1)
var projectPath = (dirname + path).replace(\/\\.\\\/\/g, '').replace(\/\\\/\\\/\/, '/')
return projectPath
}
var require = (path) => {
return execute(pathToley(path))
}
moudles[key] = { __esModule: true }
var moudle = { exports: moudles[key]}
item.code(require, moudle, moudle.exports)
return moudles[key]
}
`
return code
}
// 打包器
function collectionCodeAndDeps(failPath: string) {
// 文件的项目
const key = getProjectPath(failPath)
/**
* 解决循环依赖(DepRelation的key是所有index(索引)到的文件)
*/
if (depRelation.find(i => i.key === key)) {
return
}
// 获取文件内容
const code = readFileSync(failPath).toString()
// 转换为es5(本次改动点)
const { code: es5Code }: any = babel.transform(code, {
presets: ['@babel/preset-env']
})
// 初始化 depRealation[key]
const item: Item = {key, deps: [], code: es5Code}
depRelation.push(item)
// 将代码转为Ast
const ast = parse(code, {sourceType: 'module'})
// 分析遍历
traverse(ast, {
enter: path => {
if (path.node.type === 'ImportDeclaration') {
// item.node.source.value 是一个相对路径 如: ./a.js
const depAbsoutePath = resolve(dirname(failPath), path.node.source.value)
// 然后转译为项目路径
const depProjectPath = getProjectPath(depAbsoutePath)
// 将依赖装入DepRelation
item.deps.push(depProjectPath)
/**
* 解决嵌套依赖
*/
collectionCodeAndDeps(depAbsoutePath)
}
}
})
}
function getProjectPath(path: string) {
return relative(projectRoot, path).replace(/\\/g, '/')
}
08 对前面的loader进行优化 loader2(单一职责原则)
- webpack 里每个loader只做一件事情 现在loader文件中做了两件事 1.转字符串 和 2.代码扔到dom中
- 目前我的loader做了两件事
-
- 将css转化为js字符串
- webpack-cli 是如何调用 webpack的? 答案: webpack = require('webpack') compiler = webpack(option, callback)
- webpack 如何分析 index.js?
答案:
学习前面我知道打包器首先
分析
收集依赖
,然后打包成一个文件,并且webapck肯定也是去分析ast
匹配语法树而不是用正则,
09 plugin (imagemin-webpack-plugin)
/**
* 运行 ts-node code/06.bundleFile.ts
*/
import { writeFileSync, writevSync } from "fs"
import { resolve, relative, dirname, join } from 'path';
import { mkdir } from 'shelljs'
import * as babel from '@babel/core';
import { parse } from "@babel/parser";
import traverse from '@babel/traverse'
import { readFileSync } from 'fs'
const projectName = 'project_3'
const projectRoot = resolve(__dirname, projectName)
type DepRelation = {
key: string,
deps: string[],
code: string
}[]
type Item = {
key: string,
deps: string[],
code: string
}
// 初始化手机依赖的数组
// 类型声明
// 将入口文件的绝对路径传入函数,如 D://faiel/index.js
const depRelation: DepRelation = []
collectionCodeAndDeps(resolve(projectRoot, 'index.js'))
console.log("~ depRelation", depRelation)
const dir = `${projectName}/dist`
mkdir('-p', dir)
writeFileSync(join(dir, 'dist.js'), generatorCode())
function generatorCode() {
let code = ''
code += 'var depRelation = [' + depRelation.map(item => {
const { key,deps, code } = item
return `{
key: ${JSON.stringify(key)},
deps: ${JSON.stringify(deps)},
code: function(require, moudle, exports) {
${code}
}
}`
}).join(',') + '];\n'
code += 'var moudles = {}\n'
code += 'execute(depRelation[0].key)\n'
code += `
function execute(key) {
if (moudles[key]) { return moudles[key]}
var item = depRelation.find(i => i.key === key)
if (!item) { throw new Error(\`\${item} is not found\`) }
var pathToley = (path) => {
var dirname = key.substring(0, key.lastIndexOf('/') + 1)
var projectPath = (dirname + path).replace(\/\\.\\\/\/g, '').replace(\/\\\/\\\/\/, '/')
return projectPath
}
var require = (path) => {
return execute(pathToley(path))
}
moudles[key] = { __esModule: true }
var moudle = { exports: moudles[key]}
item.code(require, moudle, moudle.exports)
return moudles[key]
}
`
return code
}
// 打包器
function collectionCodeAndDeps(failPath: string) {
// 文件的项目
const key = getProjectPath(failPath)
/**
* 解决循环依赖(DepRelation的key是所有index(索引)到的文件)
*/
if (depRelation.find(i => i.key === key)) {
return
}
// 获取文件内容
let code = readFileSync(failPath).toString()
/**
* 这里是我们本次文件新加的处理css文件代码
*/
if (/\.css$/.test(failPath)) {
code = require('./loader2/css-loader.js')(code)
code = require('./loader2/style-loader.js')(code)
}
// 转换为es5(本次改动点)
const { code: es5Code }: any = babel.transform(code, {
presets: ['@babel/preset-env']
})
// 初始化 depRealation[key]
const item: Item = {key, deps: [], code: es5Code}
depRelation.push(item)
// 将代码转为Ast
const ast = parse(code, {sourceType: 'module'})
// 分析遍历
traverse(ast, {
enter: path => {
if (path.node.type === 'ImportDeclaration') {
// item.node.source.value 是一个相对路径 如: ./a.js
const depAbsoutePath = resolve(dirname(failPath), path.node.source.value)
// 然后转译为项目路径
const depProjectPath = getProjectPath(depAbsoutePath)
// 将依赖装入DepRelation
item.deps.push(depProjectPath)
/**
* 解决嵌套依赖
*/
collectionCodeAndDeps(depAbsoutePath)
}
}
})
}
function getProjectPath(path: string) {
return relative(projectRoot, path).replace(/\\/g, '/')
}
利用node, 通过浏览器调试代码:
eg: node --inspect-brk ./node_modules/webpack-cli/bin/cli.js