1. Create a new project (Build shelves)
npm init - y
Copy the code
The following files are required
1. Webpack.config. js as options
Exports = {mode: 'development', entry: './ SRC /index.js', // require('path') module.exports = {mode: 'development', entry: './ SRC /index.js', // require('path') module. { path: path.resolve(__dirname, 'dist'), filename: 'main.js', }, }Copy the code
Lib /webpack.js // as the webpack shelf framework extension
const fs = require('fs')
module.exports = class webpack {
constructor(options) {
const { entry, output } = options
this.entry = entry
this.output = output
}
run() {
this.parse(this.entry) // Output entry
}
parse(entryFile) {
const content = fs.readFileSync(entryFile, 'utf-8')
console.log(content) // Output the original content}}Copy the code
3. Bundle. js runs packaging through this file
const options = require('./webpack.config.js')
const Webpack = require('./lib/webpack')
new Webpack(options).run()
Copy the code
4, prepare the required packaging documents
src/index.js
import { fuck } from './a.js'
console.log(fuck)
console.log('webpack -4.0xxxx!!!')
Copy the code
src/a.js
export const fuck = "fuck you"
Copy the code
From the above code we can build a Webpack shelf
A WebPack class, a copy of the WebPack configuration, the files that need to be packaged, and the code to perform the packaging
We divided the process into three parts
Part ONE: The parse process mainly reads the contents of the packaged file. With the Babel library we parse the import statements of each file and we get the load map of each file
For example, import A.js and b.js files in main.js, we need to generate a dependency map
Save it with an object
{' main. Js: {yilai: {'. / a. s' : 'save relatively entrance path here,' the. / b.j s' : 'here is relatively entrance path'}}}Copy the code
Part 2: Traverse escape code that generates file content that the browser can execute. Convert import to require syntax
The third part: generate the packaged file, mainly deal with the require function to load other files function
2. The first goal is to extend the Parse function of the WebPack class
class webpack { // ... Parse (entryFile) {const content = fs.readfilesync (entryFile, 'utf-8') console.log(content)Copy the code
The first thing to do with code parsing:
1. Escape to ast tree
We refer to @babel/parse, which we can use to escape to ast
const parser = require('@babel/parser')
// content Indicates the content of the read file
const ast = parser.parse(content, {
sourceType: 'module'.// es6
})
Copy the code
The content of the AST is an object of type Node
{ type: 'File', start: 0, end: 86, loc: SourceLocation { start: Position { line: 1, column: 0 }, end: Position { line: 4, column: 0 }, filename: undefined, identifierName: undefined }, // ... {type: 'program ', start: 0, end: 86, //.... Body: [[Node], [Node], [Node]], //Copy the code
The objects in the ast.program.body array are line-to-code parses of type: ‘ImportDeclaration’, import statement expressions, and so on
Parsing the code into a second thing:
2. Traverse code conversion process
Traverse (AST, NULL, options) to find the import statement
console.log('traverse----------:')
const traverse = require('@babel/traverse').default
const yilai = {}
const prePath = path.dirname(entryFile)
traverse(ast, {
ImportDeclaration({ node }) {
console.log(node) // The value of type is ImportDeclaration
/ / to the node. The source. The value
// Concatenate paths
const newPath = '/' + path.join(prePath, node.source.value)
yilai[node.source.value] = newPath.replace('\ \'.'/') // Collection path}})console.log('yilai:' + yilai) // {'./a.js' : './ SRC /a.js'} gets the path relative to the entry file
Copy the code
Parsing code into a third thing:
2. Parse the AST into the desired code
const { code } = transformFromAst(ast, null, {
presets: ['@babel/preset-env'],})console.log('code:-----------')
console.log(code)
return {
entryFile,
yilai,
code,
}
Copy the code
The escaped code looks like this
var _a = require("./a.js"); // This syntax is not parsed by the browser, since it is node
console.log(_a.fuck);
console.log('webpack -4.0xxxx!!!');
Copy the code
Summary code:
class webpack {
// ...
parse(entryFile) {
const content = fs.readFileSync(entryFile, 'utf-8')
const ast = parser.parse(content, {
sourceType: 'module'.// es6
})
Ast.program. body ===> [[Node], [Node], [Node]
const yilai = {}
const prePath = path.dirname(entryFile)
traverse(ast, {
ImportDeclaration({ node }) {
console.log(node) // The value of type is ImportDeclaration
/ / to the node. The source. The value
// Concatenate paths
const newPath = '/' + path.join(prePath, node.source.value)
yilai[node.source.value] = newPath.replace('\ \'.'/')}})// Get the compiled code
const { code } = transformFromAst(ast, null, {
presets: ['@babel/preset-env'],})/ / return
return {
entryFile,
yilai,
code,
}
}
}
Copy the code
Extend the generateCode function
So the first thing we have to understand is what did Run do
run() {
const info = this.parse(this.entry)
this.modules.push(info)
for (let i = 0; i < this.modules.length; i++) {
const item = this.modules[i]
const { yilai } = item
if (yilai) {
for (let j in yilai) {
this.modules.push(this.parse(yilai[j]))
}
}
}
const obj = {}
this.modules.forEach((item) = > {
obj[item.entryFile] = item
})
console.log(obj) // Get the map
// Use parse to form the mapping
// The key in the object is the path to the relative entry
/ / value: {
// yilai: map the file load URL to the path of the relative entry file
// code: 'real code string'
// }
this.gennerateCode(obj) // Generate code and create bundle files
}
Copy the code
GennerateCode implementation
const filePath = path.join(this.output.path, this.output.filename) // Output the file directory and name
const content = Function require(module) {function newRequire(relativePath) {//.a.js =>./ SRC /a.js return require(modules[module].yilai[relativePath]) } const exports = {}; // this is a function that requires an overload. (function(require, code) {eval(code)})(newRequire, modules[module].code) return exports } return require('The ${this.entry}') /* ./src/index.js */
})(The ${JSON.stringify(obj)}) `
fs.writeFileSync(filePath, content, 'utf-8')
Copy the code
Complete code list
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const path = require('path')
const fs = require('fs')
const { transformFromAst } = require('@babel/core')
module.exports = class webpack {
constructor(options) {
const { entry, output } = options
this.entry = entry
this.output = output
this.dir = path.dirname(this.entry)
this.modules = []
}
run() {
const info = this.parse(this.entry)
this.modules.push(info)
for (let i = 0; i < this.modules.length; i++) {
const item = this.modules[i]
const { yilai } = item
if (yilai) {
for (let j in yilai) {
this.modules.push(this.parse(yilai[j]))
}
}
}
const obj = {}
this.modules.forEach((item) = > {
obj[item.entryFile] = item
})
console.log(obj) // Get the map
this.gennerateCode(obj) // Generate code and create bundle files
}
parse(entryFile) {
const content = fs.readFileSync(entryFile, 'utf-8')
const ast = parser.parse(content, {
sourceType: 'module'.// es6
})
Ast.program. body ===> [[Node], [Node], [Node]
const yilai = {}
const prePath = path.dirname(entryFile)
const currentPath = traverse(ast, {
ImportDeclaration({ node }) {
Log (node) // The value of type is ImportDeclaration
/ / to the node. The source. The value
// Concatenate paths
const newPath = '/' + path.join(prePath, node.source.value)
yilai[node.source.value] = newPath.replace(/\\/g.'/')}})const { code } = transformFromAst(ast, null, {
presets: ['@babel/preset-env'],})return {
entryFile,
yilai,
code,
}
}
gennerateCode(obj) {
const filePath = path.join(this.output.path, this.output.filename) // Output the file directory and name
const content = Function require(module) {function newRequire(relativePath) {//.a.js =>./ SRC /a.js return require(modules[module].yilai[relativePath]) } const exports = {}; (function(require, code) { eval(code) })(newRequire, modules[module].code) return exports } return require('The ${this.entry}') /* ./src/index.js */
})(The ${JSON.stringify(obj)}) `
fs.writeFileSync(filePath, content, 'utf-8')}}Copy the code
Final code verification:
node bundle.js
Copy the code
The result is the main.js file
; Function require(module) {function newRequire(relativePath) {//.a.js =>./ SRC /a.js return require(modules[module].yilai[relativePath]) } const exports = {}; (function (require, code) { eval(code) })(newRequire, modules[module].code) return exports } return require('./src/index.js') /* ./src/index.js */ })({ './src/index.js': { entryFile: './src/index.js', yilai: { './a.js': './src/a.js' }, code: '"use strict"; \n\nvar _a = require("./a.js"); \n\nconsole.log(_a.fuck); \nconsole.log(\' webpack-4.0 XXXX!! \ '); ', }, './src/a.js': { entryFile: './src/a.js', yilai: { './file/b.js': './src/file/b.js' }, code: '"use strict"; \n\nObject.defineProperty(exports, "__esModule", {\n value: true\n}); \nexports.fuck = void 0; \n\nvar _b = require("./file/b.js"); \n\nconsole.log(_b.hello); \nvar fuck = \'fuck you\'; \nexports.fuck = fuck; ', }, './src/file/b.js': { entryFile: './src/file/b.js', yilai: {}, code: '"use strict"; \n\nObject.defineProperty(exports, "__esModule", {\n value: true\n}); \nexports.hello = void 0; \nvar hello = \'hello world\'; \nexports.hello = hello; ',}})Copy the code