задний план
В настоящее время множество интерфейсных проектов, хотя использование многих новых технологий, но в конечном итоге будет скомпилирована в ES5, поэтому вы можете запустить в более низкой версии браузера, но реальность заключается в том, что текущие браузеры созревают, не компилируйте ES5 запустить, пока Google Engineer 17 лет, чтобы поделиться статьейdeploying-es2015-code-in-production-today(в интернете тоже много переводов), и дается более практичное техническое решение, но, к сожалению, большинство проектов все еще следуют старому методу разработки, что делает проект более раздутым и замедляет работу. Пришло время внести изменения, и сегодня я хочу рассказать о том, как интерфейсный проект интегрирует связанные технические решения для реализации динамической загрузки двух наборов кодов es5/es6 и, наконец, может запустить цель es6 в новой версии. браузера.
Технические решения
Техническое решение этой статьи в основном является расширением статьи, упомянутой выше.Вкратце, различия до и после преобразования заключаются в следующем:
Трансформация прежнего:
- модули js компилируются в пакеты версии es5;
- Браузер загружает js для запуска;
После трансформации:
- модули js собраны в бандлы версий es5 и es6;
- Определите, поддерживает ли браузер es6, а затем решите динамически загружать соответствующую версию бандла;
Пока вы читаете справочную статью [deploying-es2015-code-in-production-today], вы обнаружите, что он делает следующее:
<!-- Browsers with ES module support load this file. -->
<script type="module" src="main.mjs"></script>
<!-- Older browsers load this file (and module-supporting -->
<!-- browsers know *not* to load this file). -->
<script nomodule src="main.es5.js"></script>
Будет проблема, то есть некоторые браузеры скачают все js, а потом реализуют одну из поддержек, хотя автор тоже сказал, что большой проблемы нет, и привел массу причин, но я просто не хочу загрузить так много бесполезных js, поэтому моя реализация заключается не в том, чтобы напрямую импортировать набор записей, а в том, чтобы сначала определить, какую версию нужно загрузить, а затем динамически создавать теги для импорта. На самом деле общая реализация не сложна, просто нужно изменить конфигурацию webpack.
Реализовать идеи
Выход двойной версии
Здесь используется функция самого webpack для поддержки многоверсионной одновременной компиляции и вывода, как пример, приведенный на официальном сайте:
module.exports = [{
output: {
filename: './dist-amd.js',
libraryTarget: 'amd'
},
name: 'amd',
entry: './app.js',
mode: 'production',
}, {
output: {
filename: './dist-commonjs.js',
libraryTarget: 'commonjs'
},
name: 'commonjs',
entry: './app.js',
mode: 'production',
}];
Разница между двумя версиями отражена в конфигурации babel-loader.Конфигурация после преобразования выглядит следующим образом (неактуальная конфигурация опущена):
=== before ===
const configs = {
output: {
...
filename: '[name]-[fullhash:10]' +'.js',
chunkFilename: '[name]-[fullhash:10]' +'.js',
},
};
module.exports = configs;
=== after ===
function createConfigs(ecmaVersion = 'es2015') {
return {
output: {
...
filename: ecmaVersion +'_[name]-[fullhash:10]' +'.js',
chunkFilename: ecmaVersion +'_[name]-[fullhash:10]' +'.js',
},
module: {
rules: [{
loader: 'babel-loader',
options: {
presets: [[
"@babel/preset-env", {
useBuiltIns: "entry",
targets: ecmaVersion === 'es6' ? { chrome: "71" } : { chrome: "58", ie: "11" }
}]
}
}]
}
};
}
module.exports = [createConfigs(), createConfigs('es5')];
В процессе трансформации мы также столкнулись с некоторыми проблемами, такими как плагин clean-webpack-plugin, который удалит все статические ресурсы, сгенерированные в первый раз, поэтому проект не использует этот плагин, а сам реализует простой скрипт , Удалите скомпилированную целевую папку перед;
"scripts": {
"clean": "node ./scripts/cleanDist.js",
"build": "npm run clean && ...."
}
Файл cleanDist очень прост и отвечает нашим требованиям:
// scripts/cleanDist.js
const fs = require('fs');
const path = require('path');
function cleanDist(dir = path.join(__dirname, '..', 'dist')) {
fs.rmSync(dir, {
force: true,
recursive: true,
});
}
cleanDist();
После приведенного выше преобразования успешно скомпилированный файл выглядит следующим образом, а соответствующая версия известна по префиксу:
инъекция скрипта инициализации
Сгенерированный html-файл по умолчанию будет содержать все файлы бандлов входа, но согласно нашим требованиям, по умолчанию здесь не вводятся никакие бандлы (css не затрагивается), поэтому плагин HtmlWebpackPlugin нужно модифицировать:
new HtmlWebpackPlugin({
inject: DEBUG, // 本地开发,还是直接注入,生产模式则不自动注入
......
templateParameters: (compilation, assets, assetTags, options) => {
// 过滤掉所有script标签
assetTags.headTags = assetTags.headTag.filter((tag) => {
tag.tagName !== 'script';
});
assetTags.bodyTags = assetTags.bodyTags.filter((tag) => {
tag.tagName !== 'script';
});
return {
compilation,
webpackConfig: compilation.options,
htmlWebpackPlugin: {
tags: assetTags,
files: assets,
options
},
};
}
}),
Страница html-шаблона также должна быть преобразована:
<!DOCTYPE html>
<html class="borderbox">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1">
<meta name="renderer" content="webkit">
<%= htmlWebpackPlugin.tags.headTags %>
</head>
<body>
<div id="app"></div>
<%= htmlWebpackPlugin.tags.bodyTags %>
</body>
</html>
Работает это или нет, эффект, который мы хотим, таков:
<script type="text/javascript">
(function() {
// 所有版本的bundle列表
var es2015 = ["/resources/js/es2015_197-697d619283-1.0.0.js","/resources/js/es2015_main-697d619283-1.0.0.js"];
var es5 = ["/resources/js/es5_197-6cc8e71f60-1.0.0.js","/resources/js/es5_main-6cc8e71f60-1.0.0.js"];
if (support es2015) {
load(es2015);
} else {
load(es5);
}
}());
</script>
Проблема здесь в том, чтобы список js скомпилировался дважды, а две компиляции webpack независимы, поэтому эти два массива нужно кэшировать, а после завершения компиляции html-страница переписывается скриптом для достижения структуры выше .
При настройке HtmlWebpackPlugin для фильтрации всех тегов script вы фактически знаете скомпилированный список js и сохраняете его локально.
function appendTagToCache(ecmaVersion, injectTags) {
fs.appendFileSync('./cache.js', `exports.${ecmaVersion} = ${JSON.stringify(injectTags)};\n\r`);
}
......
templateParameters: (compilation, assets, assetTags, options) => {
// 保存所有js列表到本地
appendTagToCache(ecmaVersion, assets.js);
return ...;
}
Получил все списки js и соответствующие версии, так что остался только последний шаг: переписать html.
// package.json
"scripts": {
"injectScripts": "node ./scripts/injectScripts.js",
}
// scripts/injectScripts.js
const fs = require('fs');
const path = require('path');
const tagCache = require('./cache.js'); // 缓存的所有js列表文件
const htmlPath = path.join(__dirname, '../', 'dist', 'index.html');
function getVars() {
let vars = ``;
for (const [key, value] of Object.entries(tagCache)) {
vars += `var ${key} = ${JSON.stringify(value)};\n`;
}
return vars;
}
const scriptStr = `
<script type="text/javascript">
(function() {
function createScript(src) {
var s = document.createElement('script');
s.type = 'text/javascript';
s.src = src;
document.body.appendChild(s);
}
function injectTags(srcArray) {
for (var i = 0; i < srcArray.length; i++) {
createScript(srcArray[i]);
}
}
${getVars()}
var check = document.createElement('script');
if (check.noModule === false) { // support es6
injectTags(es6);
} else {
injectTags(es5);
}
}());
</script>
</body>
`;
const templateContent = fs.readFileSync(htmlPath, { encoding: 'utf8' });
fs.writeFileSync(htmlPath, templateContent.replace('</body>', scriptStr));
Наконец, не забудьте запустить после компиляцииnpm run injectScripts
, в итоге, как мы и хотели, на этом этапе задача динамической загрузки разных версий выполнена.