进行现代建造

哈Ha!

现在,每个现代浏览器都允许您使用ES6模块

乍一看,这似乎是完全没用的事情-毕竟,我们所有人都使用收集器,这些收集器用内部挑战代替了进口。 但是,如果您深入研究这些规范,事实证明,由于有了这些规范,您才能为现代浏览器提供单独的组件。

在猫的底下有个故事,我如何能够在不影响旧浏览器和我的神经的情况下将应用程序的大小减少11%。



ES6模块的功能


ES6 Modules是一个众所周知且广泛使用的模块化系统,适用于每个人:

/* someFile.js */ import { someFunc } from 'path/to/helpers.js' 

 /* helpers.js */ export function someFunc() { /* ... */ } 

要在浏览器中使用此模块化系统,需要将模块类型添加到每个脚本标签中。 较旧的浏览器会看到该类型不同于text / javascript,并且不会以JavaScript形式执行文件。

 <!--        ES6 Modules --> <script type="module" src="/path/to/someFile.js"></script> 

该规范还具有脚本标签的nomodule属性。 支持ES6模块的浏览器将忽略此脚本,而较旧的浏览器将下载并执行该脚本。

 <!--       --> <script nomodule src="/path/to/someFileFallback.js"></script> 

事实证明,您可以简单地制作两个程序集:第一个程序集具有用于现代浏览器的模块类型(Modern Build),而另一个程序集具有nomodule用于旧的浏览器(Fallback构建):

 <script type="module" src="/path/to/someFile.js"></script> <script nomodule src="/path/to/someFileFallback.js"></script> 

为什么有必要


在将项目发送到生产之前,我们必须:

  • 添加多亲。
  • 将现代代码转换为较旧的代码。

在我的项目中,我尝试支持最大数量的浏览器,有时甚至支持IE 10 。 因此,我的多文件列表包含诸如es6.promise,es6.object.values等基本内容。 但是,带有ES6模块的浏览器支持所有ES6方法,并且它们不需要额外的千字节填充。

转换也给文件大小留下了明显的痕迹:babel / preset-env使用25个转换器来覆盖大多数浏览器,每个浏览器都会增加代码的大小。 同时,对于支持ES6模块的浏览器,转换器的数量减少到9。

因此,在用于现代浏览器的程序集中,我们可以删除不必要的多文件,并减少转换器的数量,这将极大地影响生成的文件的大小!

如何添加多文件


在为现代浏览器准备Modern Build之前,值得一提的是我如何向项目中添加polyfills。



通常,项目使用core-js添加所有可能的polyfill。

当然,您不希望该库中所有88 KB的多文件,而只需要浏览器列表所需的文件。 可以使用babel / preset-env及其useBuiltIns选项使用此功能。 如果将其设置为入口,那么导入core-js将替换为浏览器所需的各个模块的导入:

 /* .babelrc.js */ module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'entry', /* ... */ }] ], /* ... */ }; 

 /*   */ import 'core-js'; 

 /*   */ import "core-js/modules/es6.array.copy-within"; import "core-js/modules/es6.array.fill"; import "core-js/modules/es6.array.find"; /*   -  */ 

但是通过这样的转变,我们仅摆脱了一部分不必要的非常古老的多聚体。 我们仍然有用于TypedArray,WeakMap和其他项目中从未使用过的奇怪事物的多义词。

为了完全解决此问题,对于useBuiltIns选项,我将值设置为use。 在编译阶段,babel / preset-env将分析文件以使用所选浏览器中不提供的功能,并在其中添加多文件:

 /* .babelrc.js */ module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'usage', /* ... */ }] ], /* ... */ }; 

 /*   */ function sortStrings(strings) { return strings.sort(); } function createResolvedPromise() { return Promise.resolve(); } 

 /*   */ import "core-js/modules/es6.array.sort"; import "core-js/modules/es6.promise"; function sortStrings(strings) { return strings.sort(); } function createResolvedPromise() { return Promise.resolve(); } 

在上面的示例中,babel / preset-env将polyphile添加到了sort函数。 您无法在JavaScript中找到将传递给该函数的对象的类型-它是具有sort函数的数组或类对象,但是babel / preset-env会为其自身选择最坏的情况并插入一个多文件。

babel / preset-env错误的情况总是发生。 要删除不必要的多亲,请不时检查要导入的多亲,并使用排除选项删除不必要的:

 /* .babelrc.js */ module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'usage', //   ,  ,     debug: true, //      exclude: ['es6.regexp.to-string', 'es6.number.constructor'], /* ... */ }] ], /* ... */ }; 

我不考虑regenerator-runtime模块,因为我使用了快速异步( 我建议大家 )。

创建现代建筑


让我们来设置Modern Build。

确保项目中有一个browserslist文件,该文件描述了所有必需的浏览器:

 /* .browserslistrc */ > 0.5% IE 10 

在构建期间添加BROWSERS_ENV环境变量,该变量可以采用fallback(对于Fallback Build)和modern(对于Modern Build)值:

 /* package.json */ { "scripts": { /* ... */ "build": "NODE_ENV=production webpack /.../", "build:fallback": "BROWSERS_ENV=fallback npm run build", "build:modern": "BROWSERS_ENV=modern npm run build" }, /* ... */ } 

现在更改babel / preset-env的配置。 要在预设中指定支持的浏览器,有一个选项目标。 她有一个特殊的缩写-esmodules。 使用它时,babel / preset-env将自动替换支持ES6模块的浏览器。

 /* .babelrc.js */ const isModern = process.env.BROWSERS_ENV === 'modern'; module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'usage', //  Modern Build     ES6 modules, //   Fallback Build     .browsersrc targets: isModern ? { esmodules: true } : undefined, /* ... */ }] ], /* ... */ ], }; 

Babel / preset-env将进一步为我们完成所有工作:它将仅选择必要的多亲和转化。

现在,我们只需从控制台发出命令即可为现代或旧版浏览器构建一个项目!

绑定现代和后备版本


最后一步是将“现代”和“后备”构建合并为一个。

我计划创建这样的项目结构:

 //     dist/ //  html- index.html //   Modern Build' modern/ ... //   Fallback Build' fallback/ ... 

在index.html中,将有来自两个程序集的必要javascript文件的链接:

 /* index.html */ <html> <head> <!-- ... --> </head> <body> <!-- ... --> <script type="module" src="/modern/js/app.540601d23b6d03413d5b.js"></script> <script nomodule src="/fallback/js/app.4d03e1af64f68111703e.js"></script> </body> </html> 

此步骤可以分为三个部分:

  1. 构建现代和后备构建在不同目录中。
  2. 获取有关必要javascript文件路径的信息。
  3. 创建带有指向所有javascript文件链接的index.html。

让我们开始吧!

在不同目录中构建现代和后备构建


首先,让我们采取最简单的步骤-我们将在dist目录中的不同目录中收集Modern和Fallback Build。

根本不可能为output.path指定所需的目录,因为我们需要webpack拥有相对于dist目录的文件路径(index.html在此目录中,所有其他依赖项都将相对于它抽出)。

创建一个特殊的函数来生成文件路径:

 /* getFilePath.js */ /*   ,       */ const path = require('path'); const isModern = process.env.BROWSERS_ENV === 'modern'; const prefix = isModern ? 'modern' : 'fallback'; module.exports = relativePath => ( path.join(prefix, relativePath) ); 

 /* webpack.prod.config.js */ const getFilePath = require('path/to/getFilePath'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { mode: 'production', output: { path: 'dist', filename: getFilePath('js/[name].[contenthash].js'), }, plugins: [ new MiniCssExtractPlugin({ filename: getFilePath('css/[name].[contenthash].css'), }), /* ... */ ], /* ... */ } 

该项目开始收集在Modern和Fallback Build的不同目录中。

获取有关必要javascript文件路径的信息


要获取有关收集的文件的信息,请连接webpack-manifest-plugin。 在程序集的最后,他将添加一个manifest.json文件,其中包含文件路径上的数据:

 /* webpack.prod.config.js */ const getFilePath = require('path/to/getFilePath'); const WebpackManifestPlugin = require('webpack-manifest-plugin'); module.exports = { mode: 'production', plugins: [ new WebpackManifestPlugin({ fileName: getFilePath('manifest.json'), }), /* ... */ ], /* ... */ } 

现在我们有了有关收集的文件的信息:

 /* manifest.json */ { "app.js": "/fallback/js/app.4d03e1af64f68111703e.js", /* ... */ } 

创建带有所有JavaScript文件链接的index.html


剩下的唯一事情就是添加index.html并将必要文件的路径插入其中。

为了生成html文件,我将在Modern Build期间使用html-webpack-plugin。 html-webpack-plugin将插入现代文件本身的路径,我将从上一步中创建的文件中获取后备文件的路径,并使用一个小的webpack插件将其粘贴到HTML中:

 /* webpack.prod.config.js */ const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModernBuildPlugin = require('path/to/ModernBuildPlugin'); module.exports = { mode: 'production', plugins: [ ...(isModern ? [ //  html-  Modern Build new HtmlWebpackPlugin({ filename: 'index.html', }), new ModernBuildPlugin(), ] : []), /* ... */ ], /* ... */ } 

 /* ModernBuildPlugin.js */ // Safari 10.1    nomodule. //      Safari   . //    : // https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc const safariFix = '!function(){var e=document,t=e.createE/* ...   ... */'; class ModernBuildPlugin { apply(compiler) { const pluginName = 'modern-build-plugin'; //    Fallback Build const fallbackManifest = require('path/to/dist/fallback/manifest.json'); compiler.hooks.compilation.tap(pluginName, (compilation) => { //    html-webpack-plugin, //      HTML compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(pluginName, (data, cb) => { //  type="module"  modern- data.body.forEach((tag) => { if (tag.tagName === 'script' && tag.attributes) { tag.attributes.type = 'module'; } }); //    Safari data.body.push({ tagName: 'script', closeTag: true, innerHTML: safariFix, }); //  fallback-   nomodule const legacyAsset = { tagName: 'script', closeTag: true, attributes: { src: fallbackManifest['app.js'], nomodule: true, defer: true, }, }; data.body.push(legacyAsset); cb(); }); }); } } module.exports = ModernBuildPlugin; 

更新package.json:

 /* package.json */ { "scripts": { /* ... */ "build:full": "npm run build:fallback && npm run build:modern" }, /* ... */ } 

使用npm run build:full命令,我们将使用Modern and Fallback Build创建一个html文件。 现在,任何浏览器都将收到能够执行的JavaScript。

将现代版本添加到您的应用程序


为了在真实的地方测试我的解决方案,我将其投入了我的一个项目。 设置配置花了我不到一个小时的时间,JavaScript文件的大小减少了11%。 实施简单,效果很好。

感谢您阅读本文的结尾!

所用材料


Source: https://habr.com/ru/post/zh-CN430950/


All Articles