Внедрить простой веб-папак

Webpack

Привет всем, я Shensanyuan.Сегодня я расскажу вам о webpack через вопрос интервью.

1. Что такое вебпак

Я считаю, что хотя многие разработчики будут настраивать веб-пакет в соответствии с официальными документами, они все еще не понимают, что делает веб-пакет и какую роль он играет во фронтенд-инжиниринге. Это действительно так? Давайте посмотрим на официальное определение:

По сути, webpack — это сборщик статических модулей для современных приложений JavaScript. Когда webpack обрабатывает приложение, он рекурсивно строит граф зависимостей, содержащий каждый модуль, необходимый приложению, а затем объединяет все эти модули в один или несколько пакетов.

Я считаю, что это определение было сказано очень ясно. Прежде всего, его суть — сборщик модулей, задачей которого является упаковка каждого модуля в соответствующий бандл. Так что же произошло между ними?

Во-вторых, введение сцены

Если вы интервьюируемый, пожалуйста, прочитайте вопрос:

В каталоге src находятся следующие файлы:

//word.js
export const word = 'hello'
//message.js
import {word} from './word.js';
const message = `say ${word}`
export default message;
//index.js
import message from './message.js'
console.log(message)

Пожалуйста, напишите bundler.js, преобразуйте код ES6 в код ES5 и упакуйте эти файлы, чтобы создать фрагмент кода, который может правильно работать в браузере. (окончательный вывод поздороваться)

Если вы действительно понимаете определение Webpack, то идея здесь должна быть очень ясной:

  • 1. Используйте babel для завершения преобразования кода и создания зависимостей для одного файла
  • 2. Создайте график зависимости
  • 3. Сгенерируйте окончательный код упаковки

Далее давайте спустимся к неясной завесе связки.

Три, шаг за шагом

Шаг 1. Преобразование кода и создание зависимостей

Чтобы преобразовать код, вам нужно использовать @babel/parser для создания абстрактного синтаксического дерева AST, затем использовать @babel/traverse для обхода AST, записи зависимостей и, наконец, для преобразования кода с помощью @babel/core и @babel/preset-env.

//先安装好相应的包
npm install @babel/parser @babel/traverse @babel/core @babel/preset-env -D
//导入包
const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const babel = require('@babel/core')
function stepOne(filename){
    //读入文件
    const content =  fs.readFileSync(filename, 'utf-8')
    const ast = parser.parse(content, {
        sourceType: 'module'//babel官方规定必须加这个参数,不然无法识别ES Module
    })
    const dependencies = {}
    //遍历AST抽象语法树
    traverse(ast, {
        //获取通过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{
        filename,//该文件名
        dependencies,//该文件所依赖的模块集合(键值对存储)
        code//转换后的代码
    }
}

Шаг 2: Создайте граф зависимостей.

//entry为入口文件
function stepTwo(entry){
    const entryModule = stepOne(entry)
    //这个数组是核心,虽然现在只有一个元素,往后看你就会明白
    const graphArray = [entryModule]
    for(let i = 0; i < graphArray.length; i++){
        const item = graphArray[i];
        const {dependencies} = item;//拿到文件所依赖的模块集合(键值对存储)
        for(let j in dependencies){
            graphArray.push(
                stepOne(dependencies[j])
            )//敲黑板!关键代码,目的是将入口模块及其所有相关的模块放入数组
        }
    }
    //接下来生成图谱
    const graph = {}
    graphArray.forEach(item => {
        graph[item.filename] = {
            dependencies: item.dependencies,
            code: item.code
        }
    })
    return graph
}
//测试一下
console.log(stepTwo('./src/index.js'))
//结果如下,是不是很神奇鸭
{ 
    './src/index.js':
   { dependencies: { './message.js': './src\\message.js' },
     code:
      '"use strict";\n\nvar _message = _interopRequireDefault(require("./message.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nconsole.log(_message["default"]);' 
   },
  './src\\message.js':
   { dependencies: { './word.js': './src\\word.js' },
     code:
      '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\nexports["default"] = void 0;\n\nvar _word = require("./word.js");\n\nvar message = "say ".concat(_word.word);\nvar _default = message;\nexports["default"] = _default;' },
  './src\\word.js':
   { dependencies: {},
     code:
      '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\nexports.word = void 0;\nvar word = \'hello\';\nexports.word = word;' 
   } 
}

Шаг 3: Генерация строк кода

//下面是生成代码字符串的操作,仔细看,不要眨眼睛哦!
function step3(entry){
    //要先把对象转换为字符串,不然在下面的模板字符串中会默认调取对象的toString方法,参数变成[Object object],显然不行
    const graph = JSON.stringify(stepTwo(entry))
    return `
        (function(graph) {
            //require函数的本质是执行一个模块的代码,然后将相应变量挂载到exports对象上
            function require(module) {
                //localRequire的本质是拿到依赖包的exports变量
                function localRequire(relativePath) {
                    return require(graph[module].dependencies[relativePath]);
                }
                var exports = {};
                (function(require, exports, code) {
                    eval(code);
                })(localRequire, exports, graph[module].code);
                return exports;//函数返回指向局部变量,形成闭包,exports变量在函数执行后不会被摧毁
            }
            require('${entry}')
        })(${graph})`
}
//最终测试
const code = step3('./src/index.js')
console.log(code)

Выполните сгенерированную строку кода на стороне браузера,

Готово! На самом деле, если вы создадите новый каталог dist и поместите эти строки в файл main.js, получится ли тот же эффект, что и при разработке npm run build в будние дни?

В это время кто-то собирается "разжечь" и сказать, что ваша тема не просит людей написать вебпак вручную? Да, но настоящий Webpack должен учитывать множество факторов, на самом деле он намного больше, но благодаря этой волне практики вы должны лучше понимать, что делает Webpack, и иметь четкое представление о Webpack, поэтому я цель достигнута. В середине будет часть кода, но это не имеет значения, я верю, что вы скоро сможете его разжевать, и вы определенно много выиграете.

Я Бог Саньюань, я надеюсь, что эта статья поможет большему количеству студентов. Ну давай же!