Webpack addresses two issues

Suppose we now have the following code

index.js

import a from './a.js'
import b from './b.js'

console.log(a.getA())

console.log(b.getB())
Copy the code

a.js

import b from './b.js'

const a = {
  value: 'a'.getA: () = > b.value + 'from a.js'
}

export default a
Copy the code

b.js

import a from './a.js'

const b = {
  value: 'b'.getB: () = > a.value + 'from b.js'
}

export default b
Copy the code

Unfortunately, code with import and export keywords can’t be run directly in the browser, so what if we want to, and have to, run this code in the browser?

How do I run import/export in the browser?

Modern browsers can support import and export with .

Compatible with the strategy

  • . Disadvantages: too many files are requested.

  • Smooth compatibility strategy: convert keywords into normal code and package all files into one file. Disadvantages: You need to write some complex code to do this.

Compile the import and export keywords (Problem 1)

In addition to the previous article,

Looking at the results of the dependencies, the main change is that the code in the dependencies is changed from the previous ES6 code to es5 code without import and export

Analyze the transformed ES5 code

Let’s take a closer look at the code after a.js becomes ES5

"use strict";
Object.defineProperty(exports."__esModule", {value: true}); / / question 1
exports["default"] = void 0;                                 / / question 2
var _b = _interopRequireDefault(require("./b.js"));          / / 1 for details
function _interopRequireDefault(obj) {                       / / 1 for details
  return obj && obj.__esModule ? obj : { "default": obj };   / / 1 for details
}
var a = {
  value: 'a'.getB: function getB() {
    return _b["default"].value + ' from a.js';               / / 1 for details}};var _default = a;                                            2 / / details
exports["default"] = _default;                               2 / / details

Copy the code

Answering questions

/ / implicit
Object.defineProperty(exports."__esModule", {value: true});

/ / show
Object.defineProperty(exports."__esModule", {
  enumerable: false.configurable: false.writable: false.value: true
});

// Why not write it like this? It may be to add a property that cannot be enumerated and cannot be modified by default. Only for your own use, as a marker
exports.__esModule = true   
Copy the code

}}}}}}}}}}}}}}}}}}}}}}} My own analysis (notes)

exports["default"] = void 0;   // What does this code mean?
Copy the code
  1. Void 0 is equivalent to undefined, a common outdated trick of old JSer
  2. Exports [‘default’
import a from './a.js'Turned out to bevar _a = _interopRequireDefault(require("./a.js")); A.eta () becomes __a["default"].getA()
Copy the code

_interopRequireDefault (module) functions

  1. _The underscore prefix is used to avoid having the same name as another variable
  2. The purpose of this function is to add to the module'default'
  3. Why do we addDefault: CommonJSModules have no default export, plus easy compatibility
  4. Internal implementation:return m && m.__esModule ? m : { "default": m }
  5. other_interop The first functions are mostly for compatibility with older code
export defaultTurned out to bevar a = {
  value: 'a'.getA: function getA() {
    return _b["default"].value + 'from a.js'; }};var _default = a;
exports["default"] = _default;


export{c} turned out to bevar c = {
  value: 'c'
};
exports.c = c;
Copy the code

Packing multiple files into one package (Question 2)

Let’s think about what this file does. Must contain all modules and then be able to execute all modules. Let’s write some pseudocode to describe what we expect.

// Build dependencies
var depRelation = [
  {
    key: 'index.js'.deps: ['a.js'.'b.js'].code: function()... },{
    key: 'a.js'.deps: [].code: function()... } // Execute the code from the entry fileexecute(depRelation[0].key)

function exection(key) {
  var item = depRelation.find(i => key === i.key)
  item.code()? // Execute the entry file code}Copy the code

Let’s start with the code we built our dependencies on. We have three main problems at the moment

  1. Previously builtdepRelationIt’s an object, and now I want to turn it into a function, why do I want to turn it into an array? Because we want to put the dependencies of the entry file first.
  2. Before thecodeIt’s still a string, and I need to turn the string into a function.
  3. executeFunction to be perfected.

Change the code string to a function

  1. incodeWrap a string around itfunction(require,module,exports){.... }.
  2. thecodeIf I write to the file, the quotes will not appear in the file.

Just like this

code = `
import a from './a.js'
import b from './b.js'
console.log(a.getA())
console.log(b.getB())
`
code2 = function(require.module.exports){
 ${code}
}
// Finally write code2 to the file, and it is ok
Copy the code

Packer completion

import * as babel from '@babel/core';
// Analyze the files that the code in index.js depends on
import { resolve, relative, dirname } from 'path'
import { readFileSync, writeFileSync } from 'fs'

import { parse } from '@babel/parser'
import traverse from '@babel/traverse';


// Set the project root directory


const projectRoot = resolve(__dirname, 'project-03')

type DepRelation = { key: string, deps: string[], code: string }[]


/ / initialization

const depRelation: DepRelation = []

function collectCodeAndDeps(filepath: string) {
  let key = getProjectPath(filepath)
  if (depRelation.find(item= > key === item.key)) {
    // Note that repeated dependencies are not necessarily circular dependencies
    return
  }
  // First read the contents of the index file
  // Convert the string code to ats
  let code = readFileSync(resolve(filepath)).toString()

  // Convert es6 code to ES5 code first
  const { code: es5Code } = babel.transform(code, {
    presets: ['@babel/preset-env']})// Use the filename of the entry file as the key of the map
  let item = {
    key,
    deps: [].code: es5Code
  }
  depRelation.push(item)

  let ast = parse(code, {
    sourceType: 'module'
  })

  / / traverse the ast

  traverse(ast, {
    enter: path= > {
      // If the current statement is import, write the value of inport to the dependency
      if (path.node.type === 'ImportDeclaration') {
        // Concatenate the directory above the current file with the dependent file from which the current file is obtained.
        let depAbsolutePath = resolve(dirname(filepath), path.node.source.value)
        // Get the path of the current file relative to the root directory
        const depProjectPath = getProjectPath(depAbsolutePath)
        // write dependency into depRelation
        item.deps.push(depProjectPath)
        // Get the real path of the dependency file and do the dependency analysis again
        collectCodeAndDeps(depAbsolutePath)
      }
    }
  })
}

collectCodeAndDeps(resolve(projectRoot, 'index.js'))

writeFileSync('dist.js', generateCode())

function generateCode() {
  let code = ' '
  // Make code into fucntion by building part of dist file based on current dependencies
  code += 'var depRelation = [' + depRelation.map(item= > {
    let { key, deps, code } = item
    return `{
      key: The ${JSON.stringify(key)},
      deps: The ${JSON.stringify(deps)},
      code: function(require, module, exports) {
        ${code}
      } 
    }`
  }).join(', ') + ']. \n'

  code += 'var modules = {}; \n'
  code += `execute(depRelation[0].key)\n`
  code += ` function execute(key) { if (modules[key]) { return modules[key] } var item = depRelation.find(i => i.key === key) if (! Item) {throw new Error(\ '\${item} is not found\')} var pathToKey = (path) => {var dirname = key.substring(0, key.lastIndexOf('/') + 1) var projectPath = (dirname + path).replace(\/\\.\\\/\/g, "). The replace (\ / \ \ \ \ \ \ \ /, / '/') return projectPath} / / execution code function of internal logic of how to deal with the require, Var require = (path) => {return execute(pathToKey(path))} modules[key] = {__esModule: true } var module = { exports: modules[key] } item.code(require, module, module.exports) return modules[key] } `
  return code
}
// Get the relative path of the file relative to the directory
/* C: \\Users\\code\\zf\\webpack\\01\\project - 01 C: \\Users\\code\\zf\\webpack\\01\\ project-01 \\index.js // The result is index.js */
function getProjectPath(path: string) {
  return relative(projectRoot, path).replace(/\\/g.'/')}Copy the code

Now let’s do the following, of course, it’s perfectly fine to import this file into the HTML file as a script, and print modules, which is used to cache the modules loaded during the execution of the code.

There are still problems

  1. Multiple repetitions in the generated code_interopRequireDefaultfunction
  2. Can only be introduced and runJS File, cannot be importedcss
  3. Can only understandimportBeyond comprehensionrequire
  4. Plugins not supported
  5. Configuration entry file anddist The file name

Source Check the source link

conclusion

  1. The main thing that the last article did was to find the dependency diagram of a piece of code at the time of execution.
  2. What this article will do is to put all the dependencies found into one file and execute all the code starting from the entry file.