Серия Webpack — Принципы

Node.js переводчик JavaScript Webpack

В этой серии будет представлен рабочий процесс веб-пакета с четырех аспектов: принцип, разработка, оптимизация и сравнение. [Например, по умолчанию используется webpack v3]

Резерв знаний

Спецификация CommonJS

// 模块引入
let moduleA = require('./a.js')

// 模块导出
module.exports = () => {}

спецификация es6

// 模块引入
import {moduleA} from './a.js'

// 模块导出
export default () => {}

опыт черного ящика

Мы можем думать о веб-пакете как о черном ящике, если его можно использовать. Давайте сначала испытаем очень простой процесс упаковки веб-пакета.

webpack

const webpack = require('webpack')
const path = require('path')

module.exports = {
  entry: './index.js',
  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, 'public')
  }
}

Запустите компиляцию и введите node_modules/.bin/webpack в командной строке, чтобы увидеть процесс упаковки.

Посмотреть результаты упаковки

О том, как запустить вебпак

Если веб-пакет установлен глобально, вы можете ввести веб-пакет прямо в командной строке.

Если вы просто устанавливаете папку проекта, вам нужно ввести node_modules/.bin/webpack

  • npx

В версии npmV5 будет отдан npx

npx будет автоматически искать исполняемый файл в текущем пакете зависимостей.Если он не сможет его найти, он перейдет к PATH, чтобы найти его. Если вы все еще не можете найти его, я установлю его для вас

Так же можно выполнить webpack через npx

npx webpack

требуемый метод

Реализовать требуемый метод

Введение модуля в спецификацию common.js требует

let getA = require('./a')

Напишите метод require самостоятельно

let fs = require('fs')
// 查找module
function myReq (myModule) {
  // 读取文件信息
  let cont = fs.readFileSync(myModule, 'utf-8')
  /* function (exports, require, module, __filename, __dirname) {
    moduel.exports = {a: 'apple'}
    return moduel.exports
  } */
  let nodeFn = new Function('exports', 'require', 'module', '__filename', '__dirname', cont + 'return module.exports')
  let module = {
    exports: {}
  }
  return nodeFn(module.exports, myReq, module, __filename, __dirname)
}
// let getA = require('./a')
let getA = myReq('./a.js')
console.log(getA, 'getA')

Идея: прочитать содержимое файла и передать несколько необходимых параметров в соответствии со спецификацией пакета узла.

  • Удалить файлы, скомпилированные webpack

Удалите неиспользуемый код из только что упакованного dist/index.js

(function(modules) {
	function myRequire(moduleId) {
		var module = {
			exports: {}
		};
    modules[moduleId].call(module.exports, module, module.exports, myRequire);
        // call 用于让  modules[moduleId] 函数执行 执行的是传入后面的参数
		return module.exports;
	}
  return myRequire(/* 下面的第一个函数参数 */);
})
([
  (function(module, exports) {
    console.log('123')
  })
]);

Посмотреть онлайн

Видно, что содержимое файлов, сгенерированных после упаковки webpack, аналогично скомпилированному методу require. Вот почему упакованный файл js можно запускать прямо в браузере.

Процесс компиляции

Объяснение общих терминов

параметр иллюстрировать
entry Вход в проект
module Каждый файл в разработке можно рассматривать как модуль
chunk кодовый блок
loader преобразователь модулей
plugin Расширение плагинов для настройки процесса упаковки webpack
bundle Окончательное завершение файла пакета

Процесс упаковки

Работающий процесс webpack — это последовательный процесс, от начала до конца следующие процессы будут выполняться последовательно.

  • инициализация параметра

Чтение и объединение параметров из файла конфигурации [webpack.config.js] и операторов оболочки.

  • начать компиляцию

Инициализировать объект компилятора. Загрузить все подключаемые модули. Выполнить метод запуска объекта, чтобы начать компиляцию.

  • Определите файл входа

Найдите все входные файлы проекта в соответствии с файлом конфигурации

  • модуль компиляции

Начните с записи и вызовите настроенный загрузчик для компиляции модуля [Идет процесс рекурсивного поиска зависимых модулей]

После компиляции модуля получить окончательный контент модуля после конвертации и зависимости между ними

  • выход ресурса

В соответствии с зависимостью между входным файлом и модулем составляется файл чанка [чанк может содержать несколько модулей] Каждый чанк будет преобразован в отдельный файл и добавлен в список вывода

  • выход

Запишите содержимое вывода в файловую систему в соответствии с настроенными параметрами вывода [путь и имя файла]

** В приведенном выше процессе WP будет транслировать определенное событие в определенный момент времени, а плагин будет выполнять определенную логику после прослушивания интересующего события **

Оптимизируйте процесс

На самом деле описанный выше процесс можно упростить до трех этапов.

webpack

Анализ исходного кода

Доступная основная библиотека

В узле есть генератор событий EventEmitter, который может отслеживать и генерировать события.

var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();
event.on('some_event', function () {
    console.log('some_event 事件触发');
});
setTimeout(function () {
    event.emit('some_event');
}, 1000);

основная библиотека webpacktapableПринцип eventEmitter аналогичен принципу EventEmitter, который запускает методы функций в каждом цикле компиляции посредством регистрации и мониторинга событий. Tapable также позволяет вам получить доступ к «эмитенту» или «производителю» события через параметры функции обратного вызова.

Посмотреть онлайн-код

компилятор основных объектов

Компилятор наследуется от tapable и может транслировать и отслеживать события.

Компилятор транслирует и прослушивает события следующим образом.


// 广播事件  params 为附带参数
compiler.apply('event-name', params)

// 监听 名为 event-name 的事件
compiler.plugin('event-name', function (params) {

})

Посмотреть 177 строк кода

Когда веб-пакет инициализируется, он передает объект компилятора в плагин, который можно использовать для доступа к основной среде веб-пакета.

Посмотреть 45 строк кода

Объект компилятора представляет собой полную конфигурацию среды веб-пакета. Этот объект создается один раз при запуске webpack и настраивает все рабочие параметры, включая опции, загрузчики и плагины.

компиляция основного объекта

компиляция наследуется от tapable, может транслировать и отслеживать события

Посмотреть 57 строк кода

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

Объект компиляции представляет текущие ресурсы модуля, скомпилированные ресурсы, измененные файлы и информацию о состоянии отслеживаемых зависимостей.

механизм реализации плагина

Как это работает

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

анализ плагина

Каждый плагин представляет собой объект JavaScript со свойством применения.

class MPlugin {
  // 这里获取用户为插件传入的配置参数
  constructor (options) {

  }
  // webpack 会调用 MPlugin 实例的apply方法 为插件实例传入 compiler 对象
  apply (compiler) {
    compiler.plugin('compilation', function (compilation) {
      // 回调函数中 传入了 compilation 对象

    })
  }
}

На этапе инициализации веб-пакета объект компилятора передается плагину.

Посмотреть 45 строк кода

написать плагин

class StartWp {
    constructor(options) {
        this.options = options
    }
    apply(compiler) {
        let {name} = this.options
        // 监听事件 这是异步的 所以要执行cb  不然会卡到这里不动了
        compiler.plugin('run', function (compilation, cb) {
            console.log('run', name)
            // 每一次重新编译的时候又会触发
            // compilation.plugin('')
            cb();
        })
        compiler.plugin('done', function (compilation) {
            console.log('done', name)
        })
    }
}
module.exports = StartWp
  • Компилятор и компиляция, переданные в плагин, одинаковы, то есть, если плагин имеет измененный объект, это повлияет на использование последующих плагинов.

  • Некоторые события являются асинхронными, поэтому при его использовании вам нужно выполнить cb(), чтобы уведомить веб-пакет о том, что мониторинг событий завершен, и продолжить выполнение, иначе вы застрянете здесь.

Как использовать этот плагин

plugins: [
  new StartWp({
    name: 'v3 - plugin '
  })
]

Напишите простую версию сборщика веб-пакетов самостоятельно.

Принцип реализации: прочитать информацию о файле в соответствии с форматом упакованного шаблона и ввести ее в указанное место.

  • С EJS

  • Выньте результат упрощенной упаковки веб-пакета в виде строкового шаблона.

Самый простой вебпак

const fs = require('fs')

// 入口文件
let input = './index.js'
// 输出地址
let output = './dist/index.js'

const ejs = require('ejs')

const getIntry = fs.readFileSync(input, 'utf-8')

let template = `(function(modules) { 
	function __webpack_require__(moduleId) {
		var module = {
			exports: {}
		};
		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
		return module.exports;
	}
	return __webpack_require__(0);
})
([
  (function(module, exports) {
    <%- getIntry %>
  })
])`

let result = ejs.render(template, {
  getIntry
})

// 将结果输出到 dist 
fs.writeFileSync(output, result)

Выполните узел webpack.0.1.0.js один раз в командной строке.

Скомпилированный результат после выполнения

Вы можете видеть, что index.js генерируется в каталоге dist и вводится в html-страницу.

myWebpack

Это завершает очень, очень простой веб-пакет

Проверьте простой веб-пакет онлайн

Добавить требуемую обработку

Если в файле записи используется require, его необходимо заменить на файл, предоставленный webpack.webpack_require

Давайте посмотрим на результат упаковки после использования require [упрощенная версия]

bundle.js

 (function(modules) {
 	function __webpack_require__(moduleId) {
 		var module = {
 			exports: {}
 		};
 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
 		return module.exports;
 	}
 	return __webpack_require__(0);
 })
 ([
  (function(module, exports, __webpack_require__) {
  __webpack_require__(1)
  console.log('index.js')
  }),
  (function(module, exports) {
    console.log(123)
  })
]);

Посмотреть онлайн-код bundle.js

Мы используем этот шаблон, чтобы переписать простой веб-пакет

const fs = require('fs')
const path = require('path')

// 入口文件
let input = './index.js'
// 输出地址
let output = './dist/index.js'

const ejs = require('ejs')

const getIntry = fs.readFileSync(input, 'utf-8')

// 将getIntry 中的 require 进行处理
const contAry = []
let dealIntry = getIntry.replace(/(require)\(['"](.+?)['"]\)/g, ($1, $2, $3, $4) => {
	let cont = fs.readFileSync($3, 'utf-8')
	contAry.push(cont)
	return $2 = `__webpack_require__(${contAry.length})`
})

let template = `(function(modules) {
 	function __webpack_require__(moduleId) {
 		var module = {
 			exports: {}
 		};
 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
 		return module.exports;
 	}
 	return __webpack_require__(0);
 })
 ([
  (function(module, exports, __webpack_require__) {
	  <%- dealIntry %>
  }),
	<% for(var i=0;i < contAry.length; i++){ %>
		(function(module, exports) {
	    <%- contAry[i] %>
	  }),
  <%}%>
])`

let result = ejs.render(template, {
  dealIntry,
  contAry
})

// 将结果输出到 dist
fs.writeFileSync(output, result)

Выполните узел webpack.1.0.0.js один раз в командной строке.

Скомпилированный результат после выполнения

myWebpack

Проверьте простой веб-пакет онлайн

Вопросы по исходному коду

  • Можно ли транслировать события в плагине, собранном самостоятельно?

Могу. Пока вы можете получить компилятор или объект компиляции, вы можете транслировать события для других плагинов для мониторинга и использования.

Справочная статья