フロントエンド アプリケーションの最適化では、ロードされるリソースのサイズを制御することが非常に重要です。ほとんどの場合、できることは、パッケージ化とコンパイルのプロセス中にサイズを制御し、リソースを分割して再利用することです。この記事では、主に webpack を使用してリソースを最適化する方法を紹介します。編集者がそれを共有し、参考にします。編集者をフォローして見てみましょう。皆さんのお役に立てれば幸いです。
前書き この記事は主に Webpack パッケージ化に基づいており、Webpack パッケージ化レベルからリソースとキャッシュを処理する方法を例として使用しています。やるべきことは、Webpack を行うことです。ビジネス コードを少量変更しながら、構成の最適化を実行します。 同時に、(webpack-contrib/webpack-bundle-analyzer) プラグインを使用して、パッケージ化されたリソースを分析することもできます。この記事では、このプラグインを使用します。主に例として使用されます。 ヒント: webpack バージョン @3.6.01. パッケージ化環境とコード圧縮まず、基本的な webpack 構成があります:// webpack.config.js const path = require('path'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const PROJECT_ROOT = path.resolve(__dirname, './'); module.exports = { entry: { index: './src/index.js' }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[chunkhash:4].js' }, module: { rules: [ { test: /\.js[x]?$/, use: 'babel-loader', include: PROJECT_ROOT, exclude: /node_modules/ } ] }, plugins: [ new BundleAnalyzerPlugin() ], resolve: { extensions: ['.js', '.jsx'] }, };パッケージ化を実行すると、プロジェクトの js が 1M を超えていることがわかります:
Hash: e51afc2635f08322670b Version: webpack 3.6.0 Time: 2769ms Asset Size Chunks Chunk Names index.caa7.js 1.3 MB 0 [emitted] [big] index現時点では、プラグイン `DefinePlugin` と `UglifyJSPlugin` を追加するだけで、ボリュームを大幅に削減できます:
// webpack.config.js ... { ... plugins: [ new BundleAnalyzerPlugin(), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') }), new UglifyJSPlugin({ uglifyOptions: { ie8: false, output: { comments: false, beautify: false, }, mangle: { keep_fnames: true }, compress: { warnings: false, drop_console: true }, } }) ] ... }この時点でのパッケージ化の出力を確認できます:
Hash: 84338998472a6d3c5c25 Version: webpack 3.6.0 Time: 9940ms Asset Size Chunks Chunk Names index.89c2.js 346 kB 0 [emitted] [big] indexコードのサイズは 1.3M から 346K に縮小されました。 (1)DefinePluginDefinePlugin を使用すると、コンパイル時に構成できるグローバル定数を作成できます。これは、開発モードとリリース モードのビルドで異なる動作を許可するのに役立つ場合があります。リリース ビルドではなく開発ビルドでログ記録を実行している場合は、グローバル定数を使用してログを記録するかどうかを決定できます。ここで DefinePlugin が登場します。これをセットアップすれば、開発およびリリース ビルドのルールは忘れて済みます。
// webpack.config.js const path = require('path'); const webpack = require('webpack'); const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const PROJECT_ROOT = path.resolve(__dirname, './'); module.exports = { entry: { index: './src/index.js' }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[chunkhash:4].js', chunkFilename: '[name].[chunkhash:4].child.js', }, module: { rules: [ { test: /\.js[x]?$/, use: 'babel-loader', include: PROJECT_ROOT, exclude: /node_modules/ } ] }, plugins: [ new BundleAnalyzerPlugin(), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') }), new UglifyJSPlugin({ uglifyOptions: { ie8: false, output: { comments: false, beautify: false, }, mangle: { keep_fnames: true }, compress: { warnings: false, drop_console: true }, } }), ], resolve: { extensions: ['.js', '.jsx'] }, };定義する必要がある主なものは `output` です。 chunkFilename` では、エクスポートされた分割コードのファイル名です。ここでは、`[name].[chunkhash:4].child.js` に設定されています。ここで、`name` はモジュール名または ID に対応します。 ` はモジュールコンテンツのハッシュです。 その後、webpack はビジネス コードに動的にインポートする 2 つの方法を提供します:
- `import('path/to/module') -> Promise`、 `require.ensure(dependency: String [] 、callback: function(require)、errorCallback: function(error)、chunkName: String)`
- 最新バージョンの webpack では、主に `import()` を使用することをお勧めします (注: import は Promise を使用するため、 Promise ポリフィルがコードでサポートされていることを確認する必要があります)。
// src/index.js function getComponent() { return import( /* webpackChunkName: "lodash" */ 'lodash' ).then(_ => { var element = document.createElement('p'); element.innerHTML = _.join(['Hello', 'webpack'], ' '); return element; }).catch(error => 'An error occurred while loading the component'); } getComponent().then(component => { document.body.appendChild(component); })
パッケージ化情報を確認できます:
Hash: d6ba79fe5995bcf9fa4d Version: webpack 3.6.0 Time: 7022ms Asset Size Chunks Chunk Names lodash.89f0.child.js 85.4 kB 0 [emitted] lodash index.316e.js 1.96 kB 1 [emitted] index [0] ./src/index.js 441 bytes {1} [built] [2] (webpack)/buildin/global.js 509 bytes {0} [built] [3] (webpack)/buildin/module.js 517 bytes {0} [built] + 1 hidden module
パッケージ化されたコードが 2 つのファイル、`index.316e.js` と `lodash.89f0.child.js` を生成することがわかります。後者は `import ` を介して渡されます。分割を実現します。
`import` 它接收一个 `path` 参数,指的是该子模块对于的路径,同时还注意到其中可以添加一行注释 `/* webpackChunkName: "lodash" */`,该注释并非是无用的,它定义了该子模块的 name,其对应与 `output.chunkFilename` 中的 `[name]`。
`import` 函数返回一个 Promise,当异步加载到子模块代码是会执行后续操作,比如更新视图等。
(1)React 中的按需加载
在 React 配合 React-Router 开发中,往往就需要代码根据路由按需加载的能力,下面是一个基于 webpack 代码动态导入技术实现的 React 动态载入的组件:
import React, { Component } from 'react'; export default function lazyLoader (importComponent) { class AsyncComponent extends Component { state = { Component: null } async componentDidMount () { const { default: Component } = await importComponent(); this.setState({ Component: Component }); } render () { const Component = this.state.Component; return Component ? <component></component> : null; } } return AsyncComponent; };
在 `Route` 中:
<switch> <route> import('./Home'))} /> <route> import('./About'))} /> <route> import('./NotFound'))} /> </route></route></route></switch>
在 `Route` 中渲染的是 `lazyLoader` 函数返回的组件,该组件在 mount 之后会去执行 `importComponent` 函数(既:`() => import('./About')`)动态加载其对于的组件模块(被拆分出来的代码),待加载成功之后渲染该组件。
使用该方式打包出来的代码:
Hash: 02a053d135a5653de985 Version: webpack 3.6.0 Time: 9399ms Asset Size Chunks Chunk Names 0.db22.child.js 5.82 kB 0 [emitted] 1.fcf5.child.js 4.4 kB 1 [emitted] 2.442d.child.js 3 kB 2 [emitted] index.1bbc.js 339 kB 3 [emitted] [big] index
三、抽离 Common 资源
(1)第三方库的长缓存
首先对于一些比较大的第三方库,比如在 React 中用到的 react、react-dom、react-router 等等,我们不希望它们被重复打包,并且在每次版本更新的时候也不希望去改变这部分的资源导致在用户端重新加载。
在这里可以使用 webpack 的 CommonsChunkPlugin 来抽离这些公共资源;
CommonsChunkPlugin 插件,是一个可选的用于建立一个独立文件(又称作 chunk)的功能,这个文件包括多个入口 chunk 的公共模块。通过将公共模块拆出来,最终合成的文件能够在最开始的时候加载一次,便存起来到缓存中供后续使用。这个带来速度上的提升,因为浏览器会迅速将公共的代码从缓存中取出来,而不是每次访问一个新页面时,再去加载一个更大的文件。
首先需要在 entry 中新增一个入口用来打包需要抽离出来的库,这里将 `'react', 'react-dom', 'react-router-dom', 'immutable'` 都给单独打包进 `vendor` 中;
之后在 plugins 中定义一个 `CommonsChunkPlugin` 插件,同时将其 `name` 设置为 `vendor` 是它们相关联,再将 `minChunks` 设置为 `Infinity` 防止其他代码被打包进来。
// webpack.config.js const path = require('path'); const webpack = require('webpack'); const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const PROJECT_ROOT = path.resolve(__dirname, './'); module.exports = { entry: { index: './src0/index.js', vendor: ['react', 'react-dom', 'react-router-dom', 'immutable'] }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[chunkhash:4].js', chunkFilename: '[name].[chunkhash:4].child.js', }, module: { rules: [ { test: /\.js[x]?$/, use: 'babel-loader', include: PROJECT_ROOT, exclude: /node_modules/ } ] }, plugins: [ new BundleAnalyzerPlugin(), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') }), new UglifyJSPlugin({ uglifyOptions: { ie8: false, output: { comments: false, beautify: false, }, mangle: { keep_fnames: true }, compress: { warnings: false, drop_console: true }, } }), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: Infinity, }), ], resolve: { extensions: ['.js', '.jsx'] }, };
运行打包可以看到:
Hash: 34a71fcfd9a24e810c21 Version: webpack 3.6.0 Time: 9618ms Asset Size Chunks Chunk Names 0.2c65.child.js 5.82 kB 0 [emitted] 1.6e26.child.js 4.4 kB 1 [emitted] 2.e4bc.child.js 3 kB 2 [emitted] index.4e2f.js 64.2 kB 3 [emitted] index vendor.5fd1.js 276 kB 4 [emitted] [big] vendor
可以看到 `vendor` 被单独打包出来了。
当我们改变业务代码时再次打包:
Hash: cd3f1bc16b28ac97e20a Version: webpack 3.6.0 Time: 9750ms Asset Size Chunks Chunk Names 0.2c65.child.js 5.82 kB 0 [emitted] 1.6e26.child.js 4.4 kB 1 [emitted] 2.e4bc.child.js 3 kB 2 [emitted] index.4d45.js 64.2 kB 3 [emitted] index vendor.bc85.js 276 kB 4 [emitted] [big] vendor
vendor 包同样被打包出来的,然而它的文件 hash 却发生了变化,这显然不符合我们长缓存的需求。
这是因为 webpack 在使用 CommoChunkPlugin 的时候会生成一段 runtime 代码(它主要用来处理代码模块的映射关系),而哪怕没有改变 vendor 里的代码,这个 runtime 仍然是会跟随着打包变化的并且打入 verdor 中,所以 hash 就会开始变化了。解决方案则是把这部分的 runtime 代码也单独抽离出来,修改之前的 `CommonsChunkPlugin` 为:
// webpack.config.js ... new webpack.optimize.CommonsChunkPlugin({ name: ['vendor', 'runtime'], minChunks: Infinity, }), ...
执行打包可以看到生成的代码中多了 `runtime` 文件,同时即使改变业务代码,vendor 的 hash 值也保持不变了。
当然这段 `runtime` 实际上非常短,我们可以直接 inline 在 html 中,如果使用的是 `html-webpack-plugin` 插件处理 html,则可以结合 [`html-webpack-inline-source-plugin`](DustinJackson/html-webpack-inline-source-plugin) 插件自动处理其 inline。
(2)公共资源抽离
在我们打包出来的 js 资源包括不同入口以及子模块的 js 资源包,然而它们之间也会重复载入相同的依赖模块或者代码,因此可以通过 CommonsChunkPlugin 插件将它们共同依赖的一些资源打包成一个公共的 js 资源。
// webpack.config.js plugins: [ new BundleAnalyzerPlugin(), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') }), new UglifyJSPlugin({ uglifyOptions: { ie8: false, output: { comments: false, beautify: false, }, mangle: { keep_fnames: true }, compress: { warnings: false, drop_console: true }, } }), new webpack.optimize.CommonsChunkPlugin({ name: ['vendor', 'runtime'], minChunks: Infinity, }), new webpack.optimize.CommonsChunkPlugin({ // ( 公共chunk(commnons chunk) 的名称) name: "commons", // ( 公共chunk 的文件名) filename: "commons.[chunkhash:4].js", // (模块必须被 3个 入口chunk 共享) minChunks: 3 }) ],
可以看到这里增加了 `commons` 的一个打包,当一个资源被三个以及以上 chunk 依赖时,这些资源会被单独抽离打包到 `commons.[chunkhash:4].js` 文件。
执行打包,看到结果如下:
Hash: 2577e42dc5d8b94114c8 Version: webpack 3.6.0 Time: 24009ms Asset Size Chunks Chunk Names 0.2eee.child.js 90.8 kB 0 [emitted] 1.cfbc.child.js 89.4 kB 1 [emitted] 2.557a.child.js 88 kB 2 [emitted] vendor.66fd.js 275 kB 3 [emitted] [big] vendor index.688b.js 64.2 kB 4 [emitted] index commons.a61e.js 1.78 kB 5 [emitted] commons
却发现这里的 `commons.[chunkhash].js` 基本没有实际内容,然而明明在每个子模块中也都依赖了一些相同的依赖。
借助 webpack-bundle-analyzer 来分析一波:
可以看到三个模块都依赖了 `lodash`,然而它并没有被抽离出来。
这是因为 CommonsChunkPlugin 中的 chunk 指的是 entry 中的每个入口,因此对于一个入口拆分出来的子模块(children chunk)是不生效的。
可以通过在 CommonsChunkPlugin 插件中配置 `children` 参数将拆分出来的子模块的公共依赖也打包进 `commons` 中:
// webpack.config.js plugins: [ new BundleAnalyzerPlugin(), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') }), new UglifyJSPlugin({ uglifyOptions: { ie8: false, output: { comments: false, beautify: false, }, mangle: { keep_fnames: true }, compress: { warnings: false, drop_console: true }, } }), new webpack.optimize.CommonsChunkPlugin({ name: ['vendor', 'runtime'], minChunks: Infinity, }), new webpack.optimize.CommonsChunkPlugin({ // ( 公共chunk(commnons chunk) 的名称) name: "commons", // ( 公共chunk 的文件名) filename: "commons.[chunkhash:4].js", // (模块必须被 3个 入口chunk 共享) minChunks: 3 }), new webpack.optimize.CommonsChunkPlugin({ // (选择所有被选 chunks 的子 chunks) children: true, // (在提取之前需要至少三个子 chunk 共享这个模块) minChunks: 3, }) ],
查看打包效果:
其子模块的公共资源都被打包到 `index` 之中了,并没有理想地打包进 `commons` 之中,还是因为 `commons` 对于的是 entry 中的入口模块,而这里并未有 3 个 entry 模块共用资源;
在单入口的应用中可以选择去除 `commons`,而在子模块的 `CommonsChunkPlugin` 的配置中配置 `async` 为 `true`:
// webpack.config.js plugins: [ new BundleAnalyzerPlugin(), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') }), new UglifyJSPlugin({ uglifyOptions: { ie8: false, output: { comments: false, beautify: false, }, mangle: { keep_fnames: true }, compress: { warnings: false, drop_console: true }, } }), new webpack.optimize.CommonsChunkPlugin({ name: ['vendor', 'runtime'], minChunks: Infinity, }), new webpack.optimize.CommonsChunkPlugin({ // (选择所有被选 chunks 的子 chunks) children: true, // (异步加载) async: true, // (在提取之前需要至少三个子 chunk 共享这个模块) minChunks: 3, }) ],
查看效果:
子模块的公共资源都被打包到 `0.9c90.child.js` 中了,该模块则是子模块的 commons。
四、tree shaking
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup。
在我们引入一个依赖的某个输出的时候,我们可能需要的仅仅是该依赖的某一部分代码,而另一部分代码则是 `unused` 的,如果能够去除这部分代码,那么最终打包出来的资源体积也是可以有可观的减小。
首先,webpack 中实现 tree shaking 是基于 webpack 内部支持的 es2015 的模块机制,在大部分时候我们使用 babel 来编译 js 代码,而 babel 会通过自己的模块加载机制处理一遍,这导致 webpack 中的 tree shaking 处理将会失效。因此在 babel 的配置中需要关闭对模块加载的处理:
// .babelrc { "presets": [ [ "env", { "modules": false, } ], "stage-0" ], ... }
然后我们来看下 webpack 是如何处理打包的代码,举例有一个入口文件 `index.js` 和一个 `utils.js` 文件:
// utils.js export function square(x) { return x * x; } export function cube(x) { return x * x * x; } " "js // index.js import { cube } from './utils.js'; console.log(cube(10)); " 打包出来的代码: " // index.bundle.js /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* unused harmony export square */ /* harmony export (immutable) */ __webpack_exports__["a"] = cube; function square(x) { return x * x; } function cube(x) { return x * x * x; }
可以看到仅有 `cube` 函数被 `__webpack_exports__` 导出来,而 `square` 函数被标记为 `unused harmony export square`,然而在打包代码中既是 `square` 没有被导出但是它仍然存在与代码中,而如何去除其代码则可以通过添加 `UglifyjsWebpackPlugin` 插件来处理。
相关推荐:
以上がWebpack を使用してリソースのメソッドとテクニックを最適化する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

Vue是一款优秀的JavaScript框架,它可以帮助我们快速构建交互性强、高效性好的Web应用程序。Vue3是Vue的最新版本,它引入了很多新的特性和功能。Webpack是目前最流行的JavaScript模块打包器和构建工具之一,它可以帮助我们管理项目中的各种资源。本文就为大家介绍如何使用Webpack打包和构建Vue3应用程序。1.安装Webpack

区别:1、webpack服务器启动速度比vite慢;由于vite启动的时候不需要打包,也就无需分析模块依赖、编译,所以启动速度非常快。2、vite热更新比webpack快;vite在HRM方面,当某个模块内容改变时,让浏览器去重新请求该模块即可。3、vite用esbuild预构建依赖,而webpack基于node。4、vite的生态不及webpack,加载器、插件不够丰富。

随着Web开发技术的不断发展,前后端分离、模块化开发已经成为了一个广泛的趋势。PHP作为一种常用的后端语言,在进行模块化开发时,我们需要借助一些工具来实现模块的管理和打包,其中webpack是一个非常好用的模块化打包工具。本文将介绍如何使用PHP和webpack进行模块化开发。一、什么是模块化开发模块化开发是指将程序分解成不同的独立模块,每个模块都有自己的作

在vue中,webpack可以将js、css、图片、json等文件打包为合适的格式,以供浏览器使用;在webpack中js、css、图片、json等文件类型都可以被当做模块来使用。webpack中各种模块资源可打包合并成一个或多个包,并且在打包的过程中,可以对资源进行处理,如压缩图片、将scss转成css、将ES6语法转成ES5等可以被html识别的文件类型。

Webpack是一款模块打包工具。它为不同的依赖创建模块,将其整体打包成可管理的输出文件。这一点对于单页面应用(如今Web应用的事实标准)来说特别有用。

配置方法:1、用导入的方法把ES6代码放到打包的js代码文件中;2、利用npm工具安装babel-loader工具,语法“npm install -D babel-loader @babel/core @babel/preset-env”;3、创建babel工具的配置文件“.babelrc”并设定转码规则;4、在webpack.config.js文件中配置打包规则即可。

web前端打包工具有:1、Webpack,是一个模块化管理工具和打包工具可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序;2、Grunt,一个前端打包构建工具;3、Gulp,用代码方式来写打包脚本;4、Rollup,ES6模块化打包工具;5、Parcel,一款速度极快、零配置的web应用程序打包器;6、equireJS,是一个JS文件和模块加载器。

web有前端,也有后端。web前端也被称为“客户端”,是关于用户可以看到和体验的网站的视觉方面,即用户所看到的一切Web浏览器展示的内容,涉及用户可以看到,触摸和体验的一切。web后端也称为“服务器端”,是用户在浏览器中无法查看和交互的所有内容,web后端负责存储和组织数据,并确保web前端的所有内容都能正常工作。web后端与前端通信,发送和接收信息以显示为网页。


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

WebStorm Mac版
便利なJavaScript開発ツール

SublimeText3 Linux 新バージョン
SublimeText3 Linux 最新バージョン

ZendStudio 13.5.1 Mac
強力な PHP 統合開発環境

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

SublimeText3 英語版
推奨: Win バージョン、コードプロンプトをサポート!
