Webpack已将自身确立为JavaScript工具链中不可或缺的一部分。它在GitHub上拥有超过55,000个星,并且被JavaScript世界中的许多大型公司使用,例如React和Angular。
但是,您无需使用前端框架,也无需进行大型项目来利用它。Webpack主要是一个捆绑器,因此,您也可以使用它来捆绑您想考虑的任何资源或资产。
在本文中,我将向您展示如何安装和配置webpack,然后使用它为具有少量资产的简单静态站点创建缩小的包。
但是为什么要这么做呢?
这样做的原因之一是最大程度地减少了您向服务器发出的HTTP请求的数量。随着平均网页的增长,您可能会包括jQuery(是的,它在2020年仍然很流行),一些字体,一些插件以及各种样式表和您自己的一些JavaScript。如果您要针对这些资产中的每一个发出网络请求,那么事情很快就会加起来,您的页面可能会变得很呆滞。将您的代码捆绑在一起可以缓解此问题。
Webpack还使简化代码变得更容易,进一步减小了代码的大小,并且使您可以按自己的喜好编写资产。例如,在本文中,我将演示如何将webpack转换为现代JavaScript到ES5。这意味着您可以使用最新的最新语法编写JavaScript(尽管可能尚不完全支持),然后为几乎可在所有地方运行的浏览器ES5提供服务。
最后,这是一个有趣的学习练习。是否在自己的项目中使用这些技术取决于您自己,但是随着学习的进行,您将对webpack的功能,如何执行以及它是否很适合您有深刻的了解。
起床并跑步
您需要做的第一件事是在计算机上安装Node和npm。如果尚未安装Node,则可以从Node网站下载它,也可以在版本管理器的帮助下下载并安装它。就个人而言,我更喜欢第二种方法,因为它允许您在多个版本的Node之间切换,并且可以消除一堆权限错误,否则可能会看到您使用管理员权限安装Node软件包。
我们还需要一个框架项目。这是我早些时候做的。要使其在您的计算机上运行,您应该从GitHub克隆项目并安装依赖项:
git clone https://github.com/sitepoint-editors/webpack-static-site-examplecd webpack-static-site-examplenpm install
这将把jQuery以及Slick Slider和Lightbox2(我们将在网站上使用的两个插件)安装node_modules
到项目根目录中的文件夹中。
之后,您可以index.html
在浏览器中打开并浏览该站点。您应该会看到以下内容:
将Webpack引入项目
接下来,我们需要安装webpack。我们可以使用以下命令执行此操作:
npm install webpack webpack-cli --save-dev
这将安装webpack和webpack CLI,并将它们添加到文件devDependency
部分package.json
:
"devDependencies": { "webpack": "^5.1.3", "webpack-cli": "^4.0.0"}
接下来,我们将创建一个dist
文件夹,其中包含捆绑的JavaScript:
mkdir dist
现在,我们可以尝试从命令行运行webpack,以查看其设置是否正确:
./node_modules/webpack/bin/webpack.js ./src/js/main.js --output-filename=bundle.js --mode=development
我们在这里所做的就是告诉webpack将其中的内容捆绑src/js/main.js
到中dist/bundle.js
。如果一切都正确安装,您应该在命令行中看到类似以下输出:
asset bundle.js 1.04 KiB [emitted] (name: main) ./src/js/main.js 192 bytes [built] [code generated] webpack 5.1.3 compiled successfully in 45 ms
然后webpack将bundle.js
在该dist
文件夹中创建一个文件。如果您在选择的文本编辑器中查看该文件,则会main.js
在底部看到一堆样板文件和内容。
自动化我们的设置
如果每次我们要运行webpack时都必须在终端中键入以上所有内容,那会很烦人。因此,让我们创建一个可以运行的npm脚本。
在中package.json
,将scripts
属性更改为如下所示:
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack ./src/js/main.js --output-filename=bundle.js --mode=development" },
创建一个Webpack配置文件
注意我们如何将文件路径传递到bundle并将输出文件路径作为webpack的参数?好吧,我们可能应该更改它,并在配置文件中指定它们。以后再使用装载机时,这将使我们的生活更轻松。
webpack.config.js
在项目根目录中创建一个文件:
touch webpack.config.js
并添加以下代码:
module.exports = { entry: './src/js/main.js', mode: 'development', output: { path: `${__dirname}/dist`, filename: 'bundle.js', },};
并将npm脚本更改为以下内容:
"scripts": { ... "build": "webpack"},
在webpack.config.js
我们导出配置对象的过程中,该对象指定了入口点,应该在其中运行webpack模式(稍后会详细介绍)以及包的输出位置。再次运行所有内容,它应该仍然像以前一样工作。
捆绑
现在我们已经有了webpack为我们生成一个包,接下来我们要做的就是将它包含在某个地方。但首先,让我们创建一个不同的入口点,以便我们可以列出我们希望webpack捆绑的资产。这将是app.js
在src/js
目录中命名的文件:
touch src/js/app.js
将以下内容添加到app.js
:
require('./main.js');
并因此更改webpack配置:
entry: './src/js/app.js',
npm run build
再次运行以重新创建捆绑包。一切都应该像以前一样工作。
现在,如果您看一看,index.html
会发现在JavaScript方面没有太多进展。在文件的底部,我们包括jQuery和一个名为的文件main.js
,该文件负责在您单击Read more…链接时显示更多信息。
让我们进行编辑,index.html
以包含包而不是main.js
。查看文件的底部。您应该看到:
<script src="./node_modules/jquery/dist/jquery.min.js"></script> <script src="./src/js/main.js"></script> </body></html>
更改为:
<script src="./node_modules/jquery/dist/jquery.min.js"></script> <script src="./dist/bundle.js"></script> </body></html>
在浏览器中刷新页面,并让自己确信 Read more…链接仍然有效。
捆绑jQuery
接下来,让我们将jQuery添加到包中。这将减少页面发出的HTTP请求的数量。为此,我们必须app.js
像这样更改文件:
window.$ = require('jquery');require('./main.js');
这里我们需要jQuery,但是当我们使用npm安装它时,我们不必包含完整路径。我们还将其通常的$
别名添加到全局window
对象,以便其他脚本可以访问它。我们需要main.js
jQuery,因为前者取决于后者,顺序很重要。
更改index.html
以删除jQuery脚本标签:
<script src="./dist/bundle.js"></script> </body></html>
运行,npm run build
然后再次在浏览器中刷新页面,以使自己确信“ 阅读更多...”链接仍然有效。是吗 好!
衡量我们的进步
谈论性能固然很好,但如果您不建立某种可衡量的指标,那么意义就很小。在我们的案例中,我们试图减少浏览器发出的HTTP请求的数量,我们可以从浏览器的开发人员工具中查看这些请求。我将以Chrome为例,但其原理对于任何现代浏览器都是相同的。
按F12打开开发人员工具,然后确保关键网络被选中的标签。然后单击并按住地址栏(带有箭头的圆圈)旁边的重新加载符号,然后选择空缓存和硬重新加载。您应该看到类似于下图的内容。
正如您在窗口底部的栏中看到的那样,正在发出八个请求(通过将jQuery添加到我们的捆绑包中,我们已经减少了一个请求),并且总共557kB的请求通过电线传输。
捆绑CSS
看一下index.html
,我们向网络发出请求的唯一另一件事是CSS。如您所见,我们将其包含main.css
在页面顶部,该文件又将导入另外四个CSS文件。
尽管在标准配置中,webpack只能处理JavaScript,但是我们也可以使用称为loader的东西来捆绑CSS。从webpack文档:
加载程序是应用于模块源代码的转换。它们允许您在处理文件时对其进行预处理
import
或“加载”。因此,加载程序有点像其他构建工具中的“任务”,并提供了一种强大的方式来处理前端构建步骤。加载程序可以将文件从其他语言(例如TypeScript)转换为JavaScript或将嵌入式图像加载为数据URL。加载程序甚至允许您import
直接从JavaScript模块执行CSS文件之类的操作!
因此,让我们改变app.js
:
// CSSrequire('../css/main.css');// JavaScriptwindow.$ = require('jquery');require('./main.js');
而且webpack.config.js
,当遇到一个以结尾的文件时,我们需要进行修改以告诉它要运行哪个加载器.css
:
module.exports = { ... module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader', ], }, ], },};
如您所见,我指定了两个加载器:css-loader和style-loader。在这两者之间,css-loader将CSS转换为JavaScript模块,而style-loader<style>
在运行时将JavaScript模块导出的CSS注入标签中。让我们同时安装:
npm install --save-dev css-loader style-loader
现在,让我们再次使用运行webpack npm run build
,看看会发生什么:
> Webpack-static-site-example@1.0.0 build /home/jim/Downloads/webpack-static-site-example> webpackasset bundle.js 349 KiB [emitted] (name: main)runtime modules 931 bytes 4 modulesmodules by path ./src/ 356 KiB modules by path ./src/css/*.css 3.96 KiB 6 modules modules by path ./src/js/*.js 294 bytes ./src/js/app.js 102 bytes [built] [code generated] ./src/js/main.js 192 bytes [built] [code generated] ./src/fonts/open-sans/OpenSans-ExtraBold.ttf 352 KiB [built] [code generated] [1 error]modules by path ./node_modules/ 290 KiB modules by path ./node_modules/css-loader/dist/runtime/*.js 2.38 KiB ./node_modules/css-loader/dist/runtime/api.js 1.57 KiB [built] [code generated] ./node_modules/css-loader/dist/runtime/getUrl.js 830 bytes [built] [code generated] ./node_modules/jquery/dist/jquery.js 281 KiB [built] [code generated] ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 6.67 KiB [built] [code generated]ERROR in ./src/fonts/open-sans/OpenSans-ExtraBold.ttf 1:0Module parse failed: Unexpected character '' (1:0)You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders(Source code omitted for this binary file) @ ./node_modules/css-loader/dist/cjs.js!./src/css/fonts.css 4:0-86 6:73-102 @ ./node_modules/css-loader/dist/cjs.js!./src/css/main.css 3:0-104 8:26-59 @ ./src/css/main.css 2:12-89 9:17-24 13:15-29 @ ./src/js/app.js 2:0-26webpack 5.1.3 compiled with 1 error in 292 ms
哦,不!炸了。检查输出,似乎中存在错误src/css/fonts.css
。如果打开该文件并查看第5行,您会看到我们包含了自定义字体(src/fonts/open-sans/OpenSans-ExtraBold.ttf
),而webpack不知道该如何处理。
但是不用担心,我们已经做到了!我们只需要使用另一个加载器。这次是url-loader,它可以将诸如字体和图像之类的资产转换为数据URL,然后可以将其添加到捆绑软件中:
module.exports = { ... module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader', ], }, { test: /\.ttf$/, use: [ 'url-loader', ], }, ], },};
当然,我们需要安装它:
npm install url-loader --save-dev
现在构建应该运行了。通过<link>
从中删除CSS标记来进行测试index.html
,重新创建分发包并刷新页面。
捆绑第三方库
现在,我们将注意力转移到photos.html
。由于我们正在使用两个库-Slick Slider和Lightbox2-这两个库都依赖jQuery,因此此页面上还有更多事情要做。幸运的是,我们可以应用我们学到的技术将它们包括在捆绑软件中。
app.js
像这样更改:
// CSS require('slick-carousel/slick/slick.css');require('slick-carousel/slick/slick-theme.css'); require('lightbox2/dist/css/lightbox.min.css'); require('../css/main.css'); // JS window.$ = require('jquery'); window.slick = require('slick-carousel'); window.lightbox = require('lightbox2'); require('./main.js');
另外,从文档的开头删除CSS include,从脚注删除脚本包括。这应该给我们:
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>I Can Haz Cheeseburger?</title> </head> <body> ... <script src="dist/bundle.js"></script> <script> $('.slick-slider').slick({ dots: true, arrows: false, infinite: true, speed: 500, fade: true, cssEase: 'linear' }); </script> </body></html>
尽管没有什么可以阻止我们在捆绑包中包含Slick初始化代码,但我将其保留在此页面上,因为我们只想在这里使用它。
现在让我们运行webpack看看会发生什么:
...ERROR in ./node_modules/slick-carousel/slick/ajax-loader.gif 1:7Module parse failed: Unexpected character '' (1:7)You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders(Source code omitted for this binary file) @ ./node_modules/css-loader/dist/cjs.js!./node_modules/slick-carousel/slick/slick-theme.css 4:0-62 10:73-102 @ ./node_modules/slick-carousel/slick/slick-theme.css 2:12-83 9:17-24 13:15-29 @ ./src/js/app.js 3:0-47....
糟糕,还有更多错误!这次似乎slick-theme.css
文件有问题,该文件正在引用GIF格式的图像。Webpack不知道如何处理GIF,因此它会举起手臂并停止工作。但是我们知道该怎么办,对吗?
将第二条规则更改webpack.config.js
为以下内容:
{ test: /\.(svg|gif|png|eot|woff|ttf)$/, use: [ 'url-loader', ],},
您会注意到我已经更改了正则表达式以匹配其他几种文件类型。Slick或Lightbox2都需要这些。再次运行webpack,并确保它完整无误。
重新运行build命令,刷新页面,并确保一切正常。
一些优化改进
我们差不多完成了,但是有几件事我们可以改进。
处理未样式化内容的闪烁
如果您在服务器上尝试此操作(仅在浏览器中打开文件可能无法使用),则在页面加载时会看到闪烁的未样式化内容。让我们在本地重现此内容。
首先,在您的系统上全局安装http-server软件包:
npm install -g http-server
然后导航到项目的根目录并发出以下命令:
http-server
这将在您的PC上启动HTTP服务器。导航至http://127.0.0.1:8080,您将像以前一样看到该站点。接下来,跳至浏览器开发工具的“网络”选项卡,然后找到用于限制连接速度的菜单。选择快速3G预设(或等效设置),然后硬刷新页面。您将看到HTML的加载方式,然后再过一两秒钟应用CSS。显然这不是最佳的。
注意:在所有现代浏览器中,应该都可以模拟慢速连接。这是有关如何在Chrome中执行操作的说明,以及这是如何在Firefox中进行操作的说明。
可以解决此问题的一种方法是利用<script>
标签的阻塞特性,并将包含内容移到文件顶部。
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>I Can Haz Cheeseburger?</title> <script src="dist/bundle.js"></script> </head> <body> ... </body></html>
这种方法可行,但是现在需要几秒钟来加载站点,这也不是完美的。
提取CSS
我们可以通过将CSS提取到其自己的包中并将其加载到页面顶部,而将JavaScript包保留在底部的位置来稍微改善这种情况。为此,我们需要mini-css-extract-plugin,所以让我们先安装它:
npm install --save-dev mini-css-extract-plugin
然后webpack.config.js
像这样更改:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');module.exports = { entry: './src/js/app.js', mode: 'development', output: { path: `${__dirname}/dist`, filename: 'bundle.js', }, plugins: [new MiniCssExtractPlugin()], module: { rules: [ { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', ], }, { test: /\.(svg|gif|png|eot|woff|ttf)$/, use: [ 'url-loader', ], }, ], },};
在这里,我们需要在文件顶部添加新插件并将其添加到plugins
数组中,然后再用MiniCssExtractPlugin的加载器替换样式加载器。现在,当您运行时npm run build
,将在dist
文件夹bundle.js
和中生成两个捆绑包main.css
。
更改index.html
并photos.html
像这样包含它们:
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>I Can Haz Cheeseburger?</title> <link rel="stylesheet" href="./dist/main.css"> </head> <body> ... <script src="./dist/bundle.js"></script> <!-- Only photos.html --> <script>$('.slick-slider').slick({ ... });</script> </body></html>
现在我们避开了FOUC,站点加载速度加快了几秒钟,这无疑是一个进步。
不同页面的不同组合
您可能已经注意到,无论用户访问哪个页面,我们都将所有资产都包含在捆绑包中并为这些捆绑包提供服务。严格来说,如果用户仅访问索引页面,则无需下载照片页面上滑块的样式和代码。
根据您要采用这种方法的程度,完全有可能为照片页面和网站上的其他页面创建单独的捆绑包。为此,您可以使用HtmlWebpackPlugin,它可以简化HTML文件的创建以服务于Webpack捆绑包。
不幸的是,该技术稍微超出了本教程的范围,但是您可以在此处找到有关如何执行此操作的出色指南。
缩小捆绑
如果您要采用“一捆一包”的方法,那么一个简单的胜利就是在生产模式下运行webpack,这将使其输出较小的缩小的包。
为此,请webpack.config.js
像这样更改:
module.exports = { entry: './src/js/app.js', mode: 'production', ...};
现在,当您运行build命令时,webpack将输出一个缩小和优化的包。这使大小bundle.js
从821.8kB减小到485.9kB。不错,考虑了所有事情。
如果您决定将捆绑软件分为JavaScript和CSS,事情会变得更加复杂。为了优化CSS,我们需要一个额外的插件—optimize-css-assets-webpack-plugin。要使用此功能,我们必须重写webpack的默认Minimalizer,这反过来意味着我们也需要指定一个JavaScript最小化器。对于此任务,terser-webpack-plugin是一个不错的选择。
让我们安装这两个:
npm install --save-dev optimize-css-assets-webpack-plugin terser-webpack-plugin
然后webpack.config.js
像这样更改 :
const MiniCssExtractPlugin = require('mini-css-extract-plugin');const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');const TerserPlugin = require('terser-webpack-plugin');module.exports = { entry: './src/js/app.js', mode: 'production', output: { path: `${__dirname}/dist`, filename: 'bundle.js', }, plugins: [new MiniCssExtractPlugin()], module: { rules: [ { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', ], }, { test: /\.(svg|gif|png|eot|woff|ttf)$/, use: [ 'url-loader', ], }, ], }, optimization: { minimize: true, minimizer: [ new TerserPlugin({ extractComments: false, }), new OptimizeCssAssetsPlugin(), ], },};
查看代码,您可以看到我们在文件顶部需要两个新插件,并且已向optimization
导出的配置对象添加了密钥。这使我们可以将Terser指定为JavaScript的最小化器,并将Optimize CSS Assets插件指定为CSS的最小化器。现在,当您运行时npm run build
,应该输出两个最小化的包。
这将束大小分别从446.6kB和338.8kB减小到144kB和336kB。对于某些CSS,JS和其他一些资产,总计480kB的捆绑包似乎有点多余,但请记住,其中的222kB是字体。
将ES6转换为ES5
您还可以安装babel-loader并让webpack通过它运行JavaScript文件,从而将现代JavaScript转换为ES5:
npm install --save-dev @babel/core babel-loader @babel/preset-env
然后在中为Javascript文件定义新规则webpack.config.js
:
{ test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], }, },},
现在,当您运行时npm run build
,JavaScript文件将通过Babel通过管道传输,并因此转换为ES5语法,该语法将在几乎所有浏览器中运行。
当然,您可以将Babel换成您喜欢的几乎所有其他JavaScript编译语言。例如,这是一个TypeScript加载器,使您可以将TypeScript添加到项目中。
结论
在本文中,我们演示了如何使用webpack捆绑一个简单的静态站点,该过程减少了HTTP请求的数量,从而使该站点可能更灵活,响应更迅速。我还演示了如何使webpack最小化结果包,从而减小文件大小,以及如何使用babel-loader将现代JavaScript转换为ES5。
但是,在我注销之前,让我们将原始配置与最终配置进行比较。以index.html
为例,该页面最初九次请求,并有319KB的有效载荷。使用Chrome中的Fast 3G预设,页面加载时间为4.14秒。相反,使用两捆设置,页面发出两个请求,有效载荷为472kB,加载时间为4.34s。
嗯……这可能会让您想知道为什么您会不厌其烦。但是,请不要忘记两个捆绑软件都由浏览器缓存,因此当您访问照片页面时,原始设置必须从服务器获取所有滑块代码,并且需要6.5秒才能完全加载。捆绑的设置已经满足了很多需求,并且可以在3.32秒内准备就绪。
即使这种策略并不适合每个人,但希望您能通过遵循该指南深入了解webpack的功能以及它的工作方式。对于那些希望进一步探索Webpack的人,我推荐“ Webpack初学者指南”,它对一些重要概念进行了更深入的介绍,例如webpack dev服务器,它将为您介绍热模块的美好世界重装。
网友评论文明上网理性发言 已有0人参与
发表评论: