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