Webpack はモジュール パッケージ化ツールです。さまざまな依存関係のモジュールを作成し、それらをすべて管理可能な出力ファイルにパッケージ化します。これは、シングル ページ アプリケーション (今日の Web アプリケーションの事実上の標準) に特に役立ちます。
2 つの単純な数学的タスク (加算と乗算) を実行できるアプリケーションがあるとします。メンテナンスを容易にするために、これらの関数を異なるファイルに分割することにします。
index.html
<html> <head> <script src="src/sum.js"></script> <script src="src/multiply.js"></script> <script src="src/index.js"></script> </head> </html>
index.js
var totalMultiply = multiply(5, 3); var totalSum = sum(5, 3); console.log('Product of 5 and 3 = ' + totalMultiply); console.log('Sum of 5 and 3 = ' + totalSum);
multiply.js
var multiply = function (a, b) { var total = 0; for (var i = 0; i < b; i++) { total = sum(a, total); } return total; };
sum.js
var sum = function (a, b) { return a + b; };
このアプリケーションの出力は次のようになります:
Product of 5 and 3 = 15 Sum of 5 and 3 = 8
ツールが何をするのに役立つのかを知らずに、ツールを使用することはできません。では、Webpack は私たちに何をもたらしてくれたのでしょうか?
モジュールを使用して依存関係を解決する
上記のコードでは、multiply.js と ## が確認できます。 #index.js はすべて sum.js に依存します。したがって、index.html が依存関係を間違った順序でインポートすると、アプリケーションは機能しません。たとえば、index.js が最初にインポートされた場合、または sum.js が multiply.js の後にインポートされた場合、エラーが発生します。
上記の例に基づいて、実際の Web アプリケーションには多くの場合、最大数十の依存関係が含まれる可能性があると想像してみましょう。また、これらの依存関係間にも依存関係が存在する可能性があります。これらの依存関係間の関係を維持するこのシーケンスは、考えると息を呑むようなものです。について。また、変数が他の依存関係によって上書きされるリスクもあり、見つけにくいバグにつながる可能性があります。 この問題点を解決するために、Webpack は変数の上書きを避けるために、依存関係をより小さいスコープのモジュールに変換します。依存関係をモジュールに変換することの追加の利点は、Webpack がこれらの依存関係を管理できることです。具体的な方法は、Webpack が必要に応じて依存モジュールを導入し、対応するスコープに一致させるというものです。パッケージ化による HTTP リクエストの数の削減
このファイルのindex.html を振り返ってみましょう。 3 つの個別のファイルをダウンロードする必要があります。もちろん、ここでのファイルは比較的小さいので対処できますが、前述の問題は依然として存在します。実際の Web アプリケーションでは、多くの依存関係が存在する可能性があり、そのためユーザーはすべての依存関係が完了するまで待たなければなりません。メイン アプリケーションを実行する前に、1 つずつダウンロードする必要があります。
そして、これは Webpack のもう 1 つの機能であるパッケージ化につながります。 Webpack では、すべての依存関係を 1 つのファイルにパッケージ化できます。つまり、ユーザーは 1 つの依存関係をダウンロードするだけで、メイン アプリケーションを実行できます。 要約すると、Webpack の主な機能はパッケージング と モジュール化 です。プラグインとローダーを使用すると、Webpack のこれら 2 つの機能を拡張できます。
require を通じて、エクスポートされたモジュールの値を読み取ることができます。
index.html
<html> <head> <script src="./dist/bundle.js""></script> </head> </html>
index.js
var multiply = require('./multiply'); var sum = require('./sum'); var totalMultiply = multiply(5, 3); var totalSum = sum(5, 3); console.log('Product of 5 and 3 = ' + totalMultiply); console.log('Sum of 5 and 3 = ' + totalSum);
multiply.js
var sum = require('./sum'); var multiply = function (a, b) { var total = 0; for (var i = 0; i < b; i++) { total = sum(a, total); } return total; }; module.exports = multiply;
sum.js
var sum = function (a, b) { return a + b; }; module.exports = sum;
sum と関数
multiply を許可することを見つけるのは難しくありません。 使用するために、これら 2 つの関数を
sum.js および multiply.js スクリプトにエクスポートしました。ここに詳細があります。お気づきかどうかはわかりませんが、index.html では、今インポートする必要があるのは bundle.js ファイル 1 つだけです。
Webpack の初期構成
上記で達成したい効果を実現するには、Webpack の初期構成を行う必要があります。var path = require('path'); module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, './dist/'), filename: 'bundle.js' } }
entry
: 这个属性表示应用的入口。入口就意味着,这是我们加载程序和程序逻辑的起点。Webpack将从这个入口开始,遍历整棵依赖树。根据遍历结果建立一个依赖间关系图,并创建需要的模块。output.path
: 这个属性表示存放打包结果的绝对路径。这里为了方便使用,我们采用了Node.js自带的函数path
,这个函数能够根据我们程序所处的位置,动态的创建绝对路径。其中,__dirname
是Node.js的一个工具变量,它表示当前文件的目录名。output.filename
: 这个属性表示打包结果的文件名。它的名字可以是任意的,只不过我们习惯叫它bundle.js
。来看看bundle.js
阅读生成的bundle.js
代码,可以给我们带来一些启发。
// the webpack bootstrap (function (modules) { // The module cache var installedModules = {}; // The require function function __webpack_require__(moduleId) { // Check if module is in cache // Create a new module (and put it into the cache) // Execute the module function // Flag the module as loaded // Return the exports of the module } // expose the modules object (__webpack_modules__) // expose the module cache // Load entry module and return exports return __webpack_require__(0); }) /************************************************************************/ ([ // index.js - our application logic /* 0 */ function (module, exports, __webpack_require__) { var multiply = __webpack_require__(1); var sum = __webpack_require__(2); var totalMultiply = multiply(5, 3); var totalSum = sum(5, 3); console.log('Product of 5 and 3 = ' + totalMultiply); console.log('Sum of 5 and 3 = ' + totalSum); }, // multiply.js /* 1 */ function (module, exports, __webpack_require__) { var sum = __webpack_require__(2); var multiply = function (a, b) { var total = 0; for (var i = 0; i < b; i++) { total = sum(a, total); } return total; }; module.exports = multiply; }, // sum.js /* 2 */ function (module, exports) { var sum = function (a, b) { return a + b; }; module.exports = sum; } ]);
从上面的代码可以看出,Webpack把每一个js脚本都封装到一个模块中,并把这些模块放进数组中。模块数组被传入Webpack的引导程序中,引导程序会把这些模块加入Webpack并执行,使得模块可用。
这里bundle.js
返回的是__webpack_require__(0)
,而这刚好对应了模块数组中的index.js
部分。基于此我们同样可以得到正确的运行结果,而不需要处理依赖管理,下载依赖的次数也仅需要一次。
Loader让Webpack更好用
Webpack仅能理解最基本的JavaScript ES5代码,它自身仅支持创建模块并打包JavaScript ES5。如果我们不仅仅局限于JavaScript ES5,例如我们想使用ES2015,这就需要告诉Webpack如何处理ES2015。这里我们的处理方式往往是,我们需要将其它语言(如TypeScript)或其它版本(如ES2015)预处理成JavaScript ES5,再让Webpack做打包。这里就需要使用Babel来做转换,把ES2015转换为ES5(当然Babel能做的事情远不止如此)。
为了说明这个过程,我们使用ES2015重写之前的功能。
index.js
import multiply from './multiply'; import sum from './sum'; const totalMultiply = multiply(5, 3); const totalSum = sum(5, 3); console.log(`Product of 5 and 3 = ${totalMultiply}`); console.log(`Sum of 5 and 3 = ${totalSum}`);
multiply.js
import sum from './sum'; const multiply = (a, b) => { let total = 0; for(let i=0;i<b;i++) { total = sum(a, total); } return total; }; export default multiply;
sum.js
const sum = (a, b) => a + b; export default sum;
这里我们使用了很多ES2015的新特性,例如箭头函数、const
关键字、模板字符串和ES2015的导入导出。ES2015的代码Webpack无法处理,所以我们需要Babel来进行转换。想要让Babel配合Webpack完成工作,我们就需要用到Babel Loader。事实上,Loader就是Webpack处理JavaScript ES5以外内容的方式。有了加载器,我们就可以让Webpack处理各式各样的文件。
想要在Webpack中使用Babel Loader,我们还需要三个Babel依赖:
babel-loader
: 提供Babel与Webpack之间的接口;
babel-core
: 提供读取和解析代码的功能,并生成对应的输出;
babel-preset-es2015
: 提供将ES2015转换为ES5的Babel规则;
在Webpack中配置Babel Loader的代码,差不多是下面这样子:
const path = require('path'); module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, './dist/'), filename: 'bundle.js' }, module: { loaders: [ { test: /.js$/, loader: 'babel-loader', exclude: /node_modules/, query: { presets: ['es2015'] } } ] } };
这段代码你可以在webpack.config.js
中找到。值得注意的是,Webpack中是支持同时存在多个Loader的,所以提供的值是一个数组。接着,还是让我们来看看每个属性代表的含义。
test
: 我们只希望Loader处理JavaScript文件,这里通过一个正则表达式匹配.js文件;loader
: 要使用的Loader,这里使用了babel-loader
;exclude
: 哪些文件不需要被处理,这里我们不希望Babel处理node_modules下的任何文件;query.presets
: 我们需要使用哪个规则,这里我们使用Babel转换ES2015的规则;配置好这些内容后,再次查看打包生成的bundle.js
,其中的内容看起来就像下面这样:
/* 2 */ function(module, exports) { var sum = function sum(a, b) { return a + b; }; module.exports = sum; }
可以看到,Babel Loader已经把ES2015的代码变成了ES5的代码。
接下来,让我们拓展上面的例子,输出计算结果。我们将在页面上创建一个body
,然后把乘积与加和的结果添加到span
中。
import multiply from './multiply'; import sum from './sum'; const totalMultiply = multiply(5, 3); const totalSum = sum(5, 3); // create the body const body = document.createElement("body"); document.documentElement.appendChild(body); // calculate the product and add it to a span const multiplyResultsSpan = document.createElement('span'); multiplyResultsSpan.appendChild(document.createTextNode(`Product of 5 and 3 = ${totalMultiply}`)); // calculate the sum and add it to a span const sumResultSpan = document.createElement('span'); sumResultSpan.appendChild(document.createTextNode(`Sum of 5 and 3 = ${totalSum}`)); // add the results to the page document.body.appendChild(multiplyResultsSpan); document.body.appendChild(sumResultSpan);
这段代码的输出结果,应该与之前是一致的,区别仅在于显示在页面上。
Product of 5 and 3 = 15 Sum of 5 and 3 = 8
我们可以使用CSS来美化这个结果,比如,我们可以让每个结果都独占一行,并且给每个结果都加上边框。为了实现这一点,我们可以使用如下的CSS代码。
span { border: 5px solid brown; display: block; }
我们需要将这个CSS也导入应用中。这里最简单的解决方案是,在我们的html中添加一个link
标签。但有了Webpack提供的强大功能,我们可以先导入它,再用Webpack来处理这个样式。
在代码中导入CSS带来的另一个好处是,开发者可以清晰的看到CSS与其使用之间的关联。这里需要注意的是,CSS的作用域并不局限于它所导入的模块本身,其作用域仍然是全局的,但从开发者的角度看,这样使用更加清晰。
import multiply from './multiply'; import sum from './sum'; // import the CSS we want to use here import './math_output.css'; const totalMultiply = multiply(5, 3); const totalSum = sum(5, 3); // create the body const body = document.createElement("body"); document.documentElement.appendChild(body); // calculate the product and add it to a span const multiplyResultsSpan = document.createElement('span'); multiplyResultsSpan.appendChild(document.createTextNode(`Product of 5 and 3 = ${totalMultiply}`)); // calculate the sum and add it to a span const sumResultSpan = document.createElement('span'); sumResultSpan.appendChild(document.createTextNode(`Sum of 5 and 3 = ${totalSum}`)); // add the results to the page document.body.appendChild(multiplyResultsSpan); document.body.appendChild(sumResultSpan);
这段代码中,与前面代码的唯一区别在于,我们导入了CSS。我们需要两个Loader来处理我们的CSS:
css-loader
: 用于处理CSS导入,具体来说,获取导入的CSS并加载CSS文件内容;
style-loader
: 获取CSS数据,并添加它们到HTML文档中;
现在我们在webpack.config.js
中的Webpack配置看起来像这样:
const path = require('path'); module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, './dist/'), filename: 'bundle.js' }, module: { loaders: [ { test: /.js$/, loader: 'babel-loader', exclude: /node_modules/, query: { presets: ['es2015'] } }, { test: /.css$/, loaders: ['style-loader', 'css-loader'] } ] } };
我们还是来看看新增的CSS配置属性所表示的内容。
test
: 我们需要告诉Loader,我们只需要它处理CSS文件。这里的正则表达式仅匹配CSS文件。loaders
: 这里与前面不同的是,我们使用了多个Loader。还有一个需要注意的细节是,Webpack从右向左处理loader,因此css-loader
处理的结果(读出CSS文件内容)会被传递给style-loader
,最终得到的是style-loader
的处理结果(将样式添加到HTML文档中)。假如我们现在需要提取CSS,并输出到一个文件中,再导入该文件。为了实现这一点,我们就要用到Plugin。Loader的作用在于,在数据被打包输出前进行预处理。而Plugin则可以阻止预处理的内容直接出现在我们的打包结果中。
我们的Webpack配置现在变成了这样:
const path = require('path'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, './dist/'), filename: 'bundle.js' }, module: { loaders: [ { test: /.js$/, loader: 'babel-loader', exclude: /node_modules/, query: { presets: ['es2015'] } }, { test: /.css$/, loader: ExtractTextPlugin.extract('css-loader') } ] }, plugins: [ new ExtractTextPlugin('style.css') ] };
在这段代码的顶部,我们导入了ExtractTextPlugin
,并使用这个插件改造了之前的CSS Loader。这里的作用是,css-loader
的处理结果不再直接返回给Webpack,而是传递给ExtractTextPlugin
。在底部我们配置了这个Plugin。
这里配置的作用是,对于传递给ExtractTextPlugin
的CSS样式数据,将会被保存在名为style.css
的文件中。这样做的好处与之前处理JavaScript时一样,我们可以将多个独立的CSS文件合并为一个文件,从而减少加载样式时的下载次数。
最终我们可以直接使用我们合并好的CSS,实现和之前一致的效果。
<html> <head> <link rel="stylesheet" href="dist/style.css"/> <script src="./dist/bundle.js""></script> </head> </html>
现在我们开始尝试向应用中添加图片,并让Webpack来协助我们处理这些图片。这里我们添加了两张图片,一个用于求和,一个用于求积。为了让Webpack有能力处理这些图片,我们使用这两个Loader:
image-webpack-loader
: 尝试帮助我们自动压缩图片体积;
url-loader
: 如果image-webpack-loader
的输出图片体积小,就内联使用这些图片,如果image-webpack-loader
的输出图片体积大,就将图像包含在输出目录中;
我们准备了两张图片,用于求积的图片(multiply.png)相对较大,用于求和的图片(sum.png)相对较小。首先,我们添加一个图片工具方法,这个方法会为我们创建图片,并将图片添加到文档中。
image_util.js
const addImageToPage = (imageSrc) => { const image = document.createElement('img'); image.src = imageSrc; image.style.height = '100px'; image.style.width = '100px'; document.body.appendChild(image); }; export default addImageToPage;
接着,让我们导入这个图片工具方法,以及我们想要添加到index.js中的图片。
import multiply from './multiply'; import sum from './sum'; // import our image utility import addImageToPage from './image_util'; // import the images we want to use import multiplyImg from '../images/multiply.png'; import sumImg from '../images/sum.png'; // import the CSS we want to use here import './math_output.css'; const totalMultiply = multiply(5, 3); const totalSum = sum(5, 3); // create the body const body = document.createElement("body"); document.documentElement.appendChild(body); // calculate the product and add it to a span const multiplyResultsSpan = document.createElement('span'); multiplyResultsSpan.appendChild(document.createTextNode(`Product of 5 and 3 = ${totalMultiply}`)); // calculate the sum and add it to a span const sumResultSpan = document.createElement('span'); sumResultSpan.appendChild(document.createTextNode(`Sum of 5 and 3 = ${totalSum}`)); // add the results to the page addImageToPage(multiplyImg); document.body.appendChild(multiplyResultsSpan); addImageToPage(sumImg); document.body.appendChild(sumResultSpan);
最后,我们还是来修改webpack.config.js
,配置两个新的Loader来处理这些图片。
const path = require('path'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, './dist/'), filename: 'bundle.js', publicPath: 'dist/' }, module: { loaders: [ { test: /.js$/, loader: 'babel-loader', exclude: /node_modules/, query: { presets: ['es2015'] } }, { test: /.css$/, loader: ExtractTextPlugin.extract('css-loader') }, { test: /.png$/, loaders: [ 'url-loader?limit=5000', 'image-webpack-loader' ] } ] }, plugins: [ new ExtractTextPlugin('style.css') ] };
还是老规矩,我们来看看新增的参数都表示什么含义。
output.publicPath
: 让url-loader
知道,保存到磁盘的文件需要添加指定的前缀。例如,我们需要保存一个output_file.png
,那么最终保存的路径应该是dist/output_file.png
;test
: 还是和之前一样,通过正则表达式匹配图像文件,非图像文件不处理;loaders
: 这里还是要再强调一下,Webpack从右向左处理loader,因此image-webpack-loader
的处理结果将会被传递给url-loader
继续处理。现在我们执行Webpack打包,会得到下面三个东西。
38ba485a2e2306d9ad96d479e36d2e7b.png bundle.js style.css
这里的38ba485a2e2306d9ad96d479e36d2e7b.png
实际上就是我们的大图片multiply.png
,较小的图片sum.png
会被内联到bundle.js
中,就像下面这样。
module.exports = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAMAAAACDyzWAAAC6FBMVEUAuv8AgL...."
这其实相当于
img.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAMAAAACDyzWAAAC6FBMVEUAuv8AgL..."
更多编程相关知识,请访问:编程视频!!
以上がウェブパックとは何ですか?仕組みの詳しい説明は?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。