Создание проектов апплетов с помощью веб-пакета

внешний интерфейс переводчик JavaScript Webpack

Содержание этой статьи

  • Как разрабатывать плагины для веб-пакетов и процесс разработки плагинов для упаковки апплетов
  • Проблемы, возникающие при разработке, и пути их решения
  • Что умеет мини-программа-webpack-loader

Разработка плагинов для веб-пакетов

Я полагаю, что студенты, которые разрабатывали плагины, видели этоWriting a PluginИли подобные статьи, потому что webpack 4 был выпущен, когда был разработан инструмент mini-program-webpack-loader, поэтому я прочитал эту статью и, кстати, прочитал следующие документы.

Если вы читали документацию, я думаю, вы должны знать:

  • Каждый плагин должен иметь метод применения, который используется движком веб-пакета для выполнения кода, который вы хотите выполнить.
  • Два важных объекта Compiler и Compilation, к ним можно привязать хуки событий (вызываются, когда webpack выполняет этот шаг), какие хуки событий можно прочитатьCompiler hooks.
  • Отношения между модулем и чанком, мы можем понять, что каждый файл будет иметь модуль, а чанк состоит из нескольких модулей.
  • Какие события есть во всем процессе упаковки webpack
  • Как написать простой загрузчик

Если вы чувствуете, что не можете начать, вы можете продолжить наблюдать за тем, как я шаг за шагом разрабатывал и улучшал mini-program-webpack-loader для упаковки небольших программ.

Апплет имеет фиксированную процедуру: во-первых, должен быть файл app.json для определения всех путей к страницам, а затем каждая страница состоит из четырех файлов: .js, .json, .wxml и .wxss. Поэтому я использую app.json в качестве записи веб-пакета.Когда веб-пакет выполняет применение плагина, он получает запись, чтобы узнать, какие страницы есть у апплета. Вероятно, процесс как на следующей картинке, небольшой плагин для упаковки программы почти завершен.

Здесь используются два плагина MultiEntryPlugin, SingleEntryPlugin. почему ты хочешь сделать это? Поскольку webpack будет определять количество сгенерированных файлов в соответствии с вашей конфигурацией записи (запись здесь — это не просто запись в конфигурации webpack, import(), require.ensure() сгенерирует запись), чтобы определить количество сгенерированных файлов, мы не хотим помещать js всех страниц. Чтобы упаковать в файл, вам нужно использовать SingleEntryPlugin для создания нового модуля ввода; и для этих статических ресурсов мы можем использовать плагин MultiEntryPlugin для обработки этих файлов как зависимость модуля ввода и настроить файловый загрузчик в загрузчике для преобразования статических ресурсов. Псевдокод выглядит следующим образом:

 const MultiEntryPlugin = require('webpack/lib/MultiEntryPlugin');
 const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
 
 class MiniPlugin {
    apply (compiler) {
        let options = compiler.options
        let context = compiler.rootContext
        let entry = options.entry
        let files = loadFiles(entry)
        let scripts = files.filter(file => /\.js$/.test(file))
        let assets = files.filter(file => !/\.js$/.test(file))
        
       new  MultiEntryPlugin(context, assets, '__assets__').apply(compiler)
        
        scripts.forEach((file => {
            let fileName = relative(context, file).replace(extname(file), '');
            new SingleEntryPlugin(context, file, fileName).apply(compiler);
        })
    }
 }

Конечно, если вы сделаете, как указано выше, вы обнаружите, что будет лишний main.js, xxx.js (имя, заполняемое при использовании MultiEntryPlugin), main.js соответствует файлу, сгенерированному настроенной записью, и xxx. .js генерируется MultiEntryPlugin. Эти файлы нам не нужны, поэтому их нужно удалить. Если вы знакомы с документацией веб-пакета, есть много мест, где мы можем изменить окончательные упакованные файлы, например, событие emit компилятора, а также могут быть реализованы события, связанные с optimizeChunks компиляции. Он существенно изменяет объектcompile.assets.

Событие emit используется в mini-program-webpack-loader для обработки такого контента, который не нужно выводить. Процесс, вероятно, выглядит следующим образом:

Конечно, упаковка небольших программ не так проста, и она должна поддерживать ссылки на wxml, wxss, wxs и пользовательские компоненты, поэтому в настоящее время для завершения необходим загрузчик, а загрузчик должен делать следующее. также очень просто - синтаксический анализ зависимых файлов, таких как .wxml, должен анализировать src компонента импорта, src wxs, .wxss должен анализировать @import, требование wxs и, наконец, использовать метод loadModule, чтобы добавить его в погрузчик. Пользовательский компонент получается непосредственно на этапе добавления записи в начале, поэтому для его завершения не требуется загрузчик. Картинка в это время:

Проблем с этим нет, но опыт разработки относительно невелик.Например, если вы добавляете кастомный компонент или страницу, вебпак незаметен, поэтому нужно проверять добавляется ли кастом при изменении .json на странице , Компоненты или новые страницы добавляются. На данный момент есть проблема: js кастомного компонента не может быть добавлен addModule, потому что js кастомного компонента нужно использовать как отдельный файл записи. Это невозможно сделать в загрузчике, поэтому попробуйте передать файл плагину (поскольку плагин выполняется до загрузчика, можно установить связь между загрузчиком и плагином). Простой и грубый способ:

// loader.js
class MiniLoader {}

module.exports = function (content) {
    new MiniLoader(this, content)
}
module.exports.$applyPluginInstance = function (plugin) {
  MiniLoader.prototype.$plugin = plugin
}

// plugin.js
const loader = require('./loader')
class MiniPlugin {
    apply (compiler) {
        loader.$applyPluginInstance(this);
    }
}

но.... Файл передается плагину, но когда вы снова используете SingleEntryPlugin, вы обнаружите, что он не действует. Поскольку веб-пакет не может воспринимать новый модуль, добавленный после сборки компилятора, он бесполезен.В настоящее время вам нужно догадаться по документу, как заставить веб-пакет воспринимать новый модуль, и выполнить запрос по ключевым словам в соответствии с событиями в документ, можно обнаружить, что обработчик события needAdditionalPass компиляции вызывается, когда компиляция завершена:

    this.emitAssets(compilation, err => {
    	if (err) return finalCallback(err);
    
    	if (compilation.hooks.needAdditionalPass.call()) {
    		compilation.needAdditionalPass = true;
    
    		const stats = new Stats(compilation);
    		stats.startTime = startTime;
    		stats.endTime = Date.now();
    		this.hooks.done.callAsync(stats, err => {
    			if (err) return finalCallback(err);
    
    			this.hooks.additionalPass.callAsync(err => {
    				if (err) return finalCallback(err);
    				this.compile(onCompiled);
    			});
    		});
    		return;
    	}
    
    	this.emitRecords(err => {
    		if (err) return finalCallback(err);
    
    		const stats = new Stats(compilation);
    		stats.startTime = startTime;
    		stats.endTime = Date.now();
    		this.hooks.done.callAsync(stats, err => {
    			if (err) return finalCallback(err);
    			return finalCallback(null, stats);
    		});
    	});
    });

Если этот обработчик событий возвращает истинное значение, вы можете заставить веб-пакет вызывать обработчик событий AdditionalPass компилятора, попробуйте добавить сюда файлы, и это сработает. В это время картина становится такой:

Конечно, есть некоторые различия в упаковке небольших программ, такие как субподряд и правильное использование splitchunk.Когда вы начнете, вы обнаружите, что есть много способов добиться желаемого эффекта.

Разработка подключаемых модулей почти завершена. В общем, webpack — это обратный вызов по-разному. Когда вы знаете, что должен делать каждый обратный вызов, веб-пакет прост в использовании. Очевидно, я не знаю, потому что я столкнулся с некоторыми проблемами во время разработки.

возникшие проблемы

1. Как поддерживать разрешающий псевдоним, node_modules в коде апплета?

Поскольку это инструмент, то, конечно, нужно делать больше вещей. Апплеты такие сложные. Если вы поддерживаете псевдоним разрешения, node_modules может облегчить поддержку проекта. Может быть, вы скажете, что это не самая основная функция webpack, нет, мы Конечно, есть надежда, что псевдонимы можно использовать в любом файле, а node_modules поддерживает не только js. Конечно, это означает, что все усложнится.Во-первых, нужно получить путь к файлу, который должен быть асинхронным, потому что разрешение в веб-пакете 4 больше не поддерживает синхронизацию. Во-вторых, имя каталога апплета не может быть node_modules, в этом случае требуется правило вычисления относительного пути, либо вывод относительно пакета, а не относительно текущего каталога проекта.

2. Слияние нескольких проектов апплетов

YouzanЧто касается небольших программ, есть версия для микроторговых центров, розничная версия и общедоступная версия.Большинство основных функций и бизнеса одинаковы.Конечно, каждую маленькую программу нельзя разработать один раз, поэтому этот инструмент имеет возможность объединить несколько небольших программ.Программа, конечно, обязательна. Такое слияние немного сложнее, чем выборка файлов из node_modules, потому что необходимо убедиться, что объединенные страницы нескольких апплетов корректны, а путь остается неизменным.

Окончательное решение этих двух проблем состоит в том, чтобы использовать каталог src корневого контекста веб-пакета в качестве базового каталога, вычислить абсолютный путь упакованного файла на основе пути каталога, а затем вычислить окончательный выходной путь в соответствии с путем каталог, в котором находится app.json файла записи.


exports.getDistPath = (compilerContext, entryContexts) => {
  /**
   * webpack 以 config 所在目录的 src 为打包入口
   * 所以可以根据该目录追溯源文件地址
   */
  return (path) => {
    let fullPath = compilerContext
    let npmReg = /node_modules/g
    let pDirReg = /^[_|\.\.]\//g

    if (isAbsolute(path)) {
      fullPath = path
    } else {
      // 相对路径:webpack 最后生成的路径,打包入口外的文件都以 '_' 表示上级目录

      while (pDirReg.test(path)) {
        path = path.substr(pDirReg.lastIndex)
        fullPath = join(fullPath, '../')
      }

      if (fullPath !== compilerContext) {
        fullPath = join(fullPath, path)
      }
    }
    // 根据 entry 中定义的 json 文件目录获取打包后所在目录,如果不能获取就返回原路径
    let contextReg = new RegExp(entryContexts.join('|'), 'g')

    if (fullPath !== compilerContext && contextReg.exec(fullPath)) {
      path = fullPath.substr(contextReg.lastIndex + 1)
      console.assert(!npmReg.test(path), `文件${path}路径错误:不应该还包含 node_modules`)
    }

    /**
     * 如果有 node_modules 字符串,则去模块名称
     * 如果 app.json 在 node_modules 中,那 path 不应该包含 node_modules 
     */

    if (npmReg.test(path)) {
      path = path.substr(npmReg.lastIndex + 1)
    }

    return path
  }
}

3. Как упаковать содержимое, от которого зависит подпакет, отдельно в подпакет

Способ решения этой проблемы состоит в том, чтобы добавить файл записи этого чанка в зависимый модуль каждого чанка через событие optimChunks, а затем проверить количество зависимых модулей в тестовой конфигурации splitChunk. Если есть только один, и он зависит от подпакета, он упаковывается в подпакет.

4. Webpack не поддерживает один файл

Это открытая проблема, которая не кажется такой удобной при попытке использовать веб-пакет для поддержки отдельных файлов:

  • После того, как один файл разбит на четыре файла, вы можете использовать emitFile и addDependency для создания файлов, но созданные файлы не будут выполняться загрузчиком.
  • Использование loadModule сообщит об ошибке, поскольку файл не существует в файловой системе.

напиши в конце

Наконец, конечно же, нужно представить, что умеет мини-программа-вебпак-загрузчик.

Инструмент в основном решает следующие задачи:

  • Апплеты не поддерживают npm
  • Каталоги вложены слишком глубоко, а пути неуправляемы
  • Старые проекты слишком большие, а использовать новые инструменты слишком дорого
  • Предоставлять общие предложения по оптимизации в крупномасштабных проектах апплетов

повторить

  • Вы можете напрямую использовать npm для загрузкиzanui-weappохватывать
  • Вы можете попрощаться с "../../../../../xxx"
  • Вы можете использовать mpVue, таро для написания новых функций, не беспокойтесь о несовместимости

Наконец, оставьте адрес документа в конце:mini-program-webpack-loader