Webpack 快速入门指南(一)

简介

webpack 是一个前端资源加载/打包工具。

webpack 是一个 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

webpack 可以将多种静态资源(js、css、less、sass等) 转换、打包成一个或多个静态文件。

在 webpack 中,"一切皆模块”,它把 js 文件视作模块,其他的(如 css、png、sass 等)文件也视作模块。

webpack 4.0.0 的特性

  • CLI 被单独分离到 webpack-cli 包,需单独安装。
  • 默认将 ./src/index.js 作为入口,将 ./dist/main.js 作为输出。
  • 引入开发(development)和生产(production)模式,必须通过 --mode 选项指定一种模式。
  • 移除了一些常用的内置插件,可使用新的内置插件代替。

主要概念

webpack 是高度可配置的,默认的配置文件是 webpack.config.js,你需要先理解四个主要概念:

  • 入口(entry)
  • 输出(output)
  • loader
  • 插件(plugins)

入口(entry)

入口 指定了 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。

进入入口后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

每个依赖项随即被处理,最后输出到称之为 bundles 的文件中。

可以通过在 webpack 配置中配置 entry 属性,来指定一个或多个入口起点。

下面是一个 entry 配置的最简单例子:

webpack.config.js

module.exports = {
  entry: './path/to/my/entry/file.js'
};

输出(output)

输出(output)告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件。

你可以在配置中通过 output 属性进行设定。

const path = require('path');

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js'
  }
};

loader

loader 使 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包功能,对它们进行处理。

loader 能够 import 导入任何类型的模块(例如 .css 文件)。

在 webpack 的配置中 loader 有两个目标:

  • test 属性,指定哪些文件需要被 loader 转换。
  • use 属性,指定进行转换时,应该使用哪个 loader。
const path = require('path');

const config = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  }
};

module.exports = config;

上述示例中,对一个单独的 module 对象定义了 rules 属性,rules 属性里面包含两个必须属性:test 和 use。它告诉 webpack 的编译器:当碰到「require()/import 语句中被解析为 '.txt' 的路径」时,在对它打包之前,先使用 raw-loader 进行转换。

注意: 在 webpack 配置中定义 loader 时,要定义在 module.rules 中,而不是 rules。

插件(plugins)

loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。

插件的功能包括,从打包优化和压缩,一直到重新定义环境中的变量。

插件接口功能极其强大,可以用来处理各种各样的任务。

想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以自定义参数。

const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 用于访问内置插件
const path = require('path');

const config = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};

module.exports = config;

安装

前提条件

在安装 webpack 之前,你必须先安装 node.js,然后通过 node.js 的 npm 工具来安装 webpack。

本地安装

要安装最新版本或特定版本的 webpack ,请运行以下命令之一:

npm install --save-dev webpack

npm install --save-dev webpack@<version>

推荐本地安装。这可以使我们在引入破坏式变更的依赖时,更容易分别升级项目。通常,webpack 通过运行一个或多个 npm 脚本,会在本地 node_modules 目录中查找安装的 webpack:

"scripts": {
    "start": "webpack --config webpack.config.js"
}

说明:当你在本地安装 webpack 后,你能够从 node_modules/.bin/webpack 访问它的 bin 版本。

注意: webpack 4.0.0 中,将 CLI 单独分离到了 webpack-cli 包中,如果想在 CLI 中运行 webpack,就必须单独安装 webpack-cli。

npm install --save-dev webpack-cli

全局安装

npm install --global webpack

npm install --global webpack-cli

初步使用

webpack 用于编译 JavaScript 模块。一旦完成安装,你就可以通过 webpack 的 CLI 或 API 与 webpack 进行交互。

基本安装

首先,我们创建一个项目目录 webpack-demo ,初始化 npm,以及在本地安装 webpack:

mkdir webpack-demo && cd webpack-demo

npm init -y

npm install --save-dev webpack

单独安装 webpack-cli:

npm install --save-dev webpack-cli

并将 webpack-cli 的命令文件所在目录(D:\phpStudy\WWW\webpack-demo\node_modules\.bin),添加至系统的环境变量 PATH 中。如此,就可以在命令行中的任何工作目录直接使用 webpack 命令了。

然后,在 webpack-demo 目录中,创建 index.html,内容如下:

<html>
  <head>
    <title>Getting Started</title>
    <script src="https://unpkg.com/[email protected]"></script>
  </head>
  <body>
    <script src="./src/index.js"></script>
  </body>
</html>

在 webpack-demo 中,新建 src 目录,并在 src 目录中创建 index.js ,内容如下:

function component() {
  var element = document.createElement('div');

  // Lodash(目前通过一个 script 脚本引入)对于执行这一行是必需的
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');

  return element;
}

document.body.appendChild(component());

在此示例中,<script> 标签之间存在隐式依赖关系。index.js 文件执行之前,还依赖于页面中引入的 lodash。之所以说是隐式的是因为 index.js 并未显式声明需要引入 lodash,只是假定推测已经存在一个全局变量 _。

使用这种方式去管理 JavaScript 项目会有一些问题:

  • 无法立即体现,脚本的执行依赖于外部扩展库(external library)。
  • 如果依赖不存在,或者引入顺序错误,应用程序将无法正常运行。
  • 如果依赖被引入但是并没有使用,浏览器将被迫下载无用代码。

让我们使用 webpack 来管理这些脚本。

创建一个 bundle 文件

首先,我们稍微调整下目录结构,在 webpack-demo 中新建 dist 目录,然后将 index.html 复制一份拷贝进去。

要在 index.js 中打包 lodash 依赖,我们需要在本地安装 lodash 。

npm install --save lodash

然后,在我们的脚本 src/index.js 中 import。

import _ from 'lodash';

function component() {
    var element = document.createElement('div');

    // Lodash, now imported by this script
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');

    return element;
}

document.body.appendChild(component());

现在,由于将通过打包来合成脚本,我们必须更新 dist/index.html 文件。

<html>
<head>
    <title>Getting Started</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>

在这个设置中,index.js 显式要求引入的 lodash 必须存在,然后将它绑定为 _(没有全局作用域污染)。通过声明模块所需的依赖,webpack 能够利用这些信息去构建依赖图,然后使用图生成一个优化过的,会以正确顺序执行的 bundle。

然后,创建 bundle 文件。

webpack --mode development --entry ./src/index.js --output ./dist/bundle.js

在浏览器中访问 http://localhost/webpack-demo/dist/index.html ,如果一切都正常,你应该能看到以下文本:'Hello webpack'。

使用 webpack 的配置文件

大多数项目会需要很复杂的设置,这就是为什么 webpack 要支持配置文件。这比在终端中输入大量命令要高效的多,所以让我们创建一个取代以上使用 CLI 选项方式的配置文件:

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};

现在,让我们通过 webpack.config.js 配置文件,再次执行构建:

webpack --mode development --config webpack.config.js

说明: 如果 webpack.config.js 文件存在,webpack 命令将默认选择使用它。

我们在这里使用 --config 选项只是向你表明,可以传递任何名称的配置文件。这对于需要拆分成多个文件的复杂配置非常有用。

NPM 脚本(npm scripts)

考虑到用 CLI 这种方式来运行本地的 webpack 不是特别方便,我们可以设置一个快捷方式。在 package.json 文件中添加一个 npm 脚本(npm script):

{
  ...
  "scripts": {
    "dev": "webpack --mode development"
  },
  ...
}

说明: scripts 的值是一个 json,该 json 可以包含多键值对,其中,键代表可以通过 npm run 来运行的别名(如 dev),而值代表可以在 CLI 中运行的命令及其选项。

现在,来测试你的别名是否可以正常运行:

npm run dev

通过向 npm run dev 命令和你的参数之间添加两个中横线,可以将自定义参数传递给 webpack。例如:

npm run dev -- --colors

结论

现在,你已经实现了一个基本的构建过程。下面来学习如何通过 webpack 来管理资源,例如图片、字体。

管理资源

如果你是从开始一直遵循指南的示例,就会有一个小项目,显示 "Hello webpack"。现在我们尝试整合一些其他资源,比如图像,看看 webpack 如何处理。

webpack 最出色的功能之一就是,除了 JavaScript,还可以通过 loader 引入任何其他类型的文件。让我们从 CSS 开始起步。

加载 CSS

为了从 JavaScript 模块中 import 一个 CSS 文件,你需要在 module 配置中 安装并添加 style-loader 和 css-loader:

npm install --save-dev style-loader css-loader

修改 webpack.config.js 配置文件

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            }
        ]
    }
};

webpack 根据正则表达式,来确定应该查找哪些文件,并将其提供给指定的 loader。在这种情况下,以 .css 结尾的全部文件,都将被提供给 style-loader 和 css-loader。

这使你可以在依赖于此样式的文件中 import './style.css'。当该模块运行时,含有 CSS 字符串的 <style> 标签,将被插入到 html 文件的 <head> 中。

在项目中添加一个 style.css 文件:

src/style.css

.hello {
  color: red;
}

并将其导入到我们的 index.js 中:

src/index.js

import _ from 'lodash';
import './style.css';

function component() {
    var element = document.createElement('div');

    // Lodash, now imported by this script
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    element.classList.add('hello');

    return element;
}

document.body.appendChild(component());

现在运行构建命令:

npm run dev

再次在浏览器中打开 index.html,你应该看到 Hello webpack 现在的样式是红色。要查看 webpack 做了什么,请检查页面(不要查看页面源代码,因为它不会显示结果,可通过浏览器的调试工具进行查看)的 head 标签。它会包含我们在 index.js 中导入的 style 块元素。

请注意,在多数情况下,你也可以进行 CSS 分离,以便在生产环境中节省加载时间。最重要的是,现有的 loader 可以支持任何你可以想到的 CSS 处理器风格 - postcss, sass 和 less 等。

加载图片

假想,现在我们正在下载 CSS,但是我们的背景和图标这些图片,要如何处理呢?使用 file-loader,我们可以轻松地将这些内容混合到 CSS 中:

npm install --save-dev file-loader

webpack.config.js

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [
                    'file-loader'
                ]
            }
        ]
    }
};

现在,当你 import MyImage from './my-image.png',该图像将被处理并添加到 output 目录,并且 MyImage 变量将包含该图像在处理后的最终 url。当使用 css-loader 时,如上所示,你的 CSS 中的 url('./my-image.png') 会使用类似的过程去处理。loader 会识别这是一个本地文件,并将 './my-image.png' 路径,替换为输出目录中图像的最终路径。html-loader 以相同的方式处理 。

我们向项目添加一个图像,然后看它是如何工作的,你可以使用任何你喜欢的图像:

首先,在项目的 src 目录中,添加一张 icon.png 图片文件。

然后,修改 src/index.js 文件:

import _ from 'lodash';
import './style.css';
import Icon from './icon.png';

function component() {
    var element = document.createElement('div');

    // Lodash, now imported by this script
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    element.classList.add('hello');

    // 将图像添加到我们现有的 div
    var myIcon = new Image();
    myIcon.src = Icon;

    element.appendChild(myIcon);

    return element;
}

document.body.appendChild(component());

然后,修改 src/style.css 文件:

.hello {
    color: red;
    background: url('./icon.png');
}

最后,进行构建。

npm run dev

如果一切顺利,和 Hello webpack 文本旁边的 img 元素一样,现在看到的图标是重复的背景图片。如果你检查此元素,你将看到实际的文件名已更改为像 5c999da72346a995e7e2718865d019c8.png 一样的哈希字符串,并且,在 dist 文件夹中会创建一个同名的图片文件 。这意味着 webpack 在 src 文件夹中找到该图片文件,并成功处理过它!

想要压缩和优化图片,请查看 image-webpack-loader 和 url-loader,以了解更多关于增强加载处理图片的功能。

加载字体

那么,像字体这样的其他资源如何处理呢?file-loader 和 url-loader 可以接收并加载任何文件,然后将其输出到构建目录。这就是说,我们可以将它们用于任何类型的文件,包括字体。让我们更新 webpack.config.js 来处理字体文件:

webpack.config.js

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [
                    'file-loader'
                ]
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: [
                    'file-loader'
                ]
            }
        ]
    }
};

在项目 的 src 目录中添加一个 my-font.ttf 字体文件

通过配置好 loader 并将字体文件放在合适的地方,你可以通过一个 @font-face 声明引入。本地的 url(...) 指令会被 webpack 获取处理,就像它处理图片资源一样:

src/style.css

@font-face {
    font-family: 'MyFont';
    src: url('./my-font.ttf') format('ttf');
    font-weight: 600;
    font-style: normal;
}


.hello {
    color: red;
    font-family: 'MyFont';
    background: url('./icon.png');
}

现在让我们重新构建来看看 webpack 是否处理了我们的字体:

npm run dev

重新打开 index.html 看看我们的 Hello webpack 文本显示是否换上了新的字体。如果一切顺利,你应该能看到变化。

加载数据

此外,可以加载的有用资源还有数据,如 JSON 文件,CSV、TSV 和 XML。类似于 NodeJS,JSON 支持实际上是内置的,也就是说 import Data from './data.json' 默认将正常运行。要导入 CSV、TSV 和 XML,你可以使用 csv-loader 和 xml-loader。让我们处理这三类文件:

npm install --save-dev csv-loader xml-loader

webpack.config.js

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [
                    'file-loader'
                ]
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: [
                    'file-loader'
                ]
            },
            {
                test: /\.(csv|tsv)$/,
                use: [
                    'csv-loader'
                ]
            },
            {
                test: /\.xml$/,
                use: [
                    'xml-loader'
                ]
            }
        ]
    }
};

给你的项目添加一些数据文件:

src/data.xml

<?xml version="1.0" encoding="UTF-8"?>
<note>
    <to>Mary</to>
    <from>John</from>
    <heading>Reminder</heading>
    <body>Call Cindy on Tuesday</body>
</note>

现在,你可以 import 这四种类型的数据(JSON, CSV, TSV, XML)中的任何一种,所导入的 Data 变量将包含可直接使用的已解析 JSON:

src/index.js

import _ from 'lodash';
import './style.css';
import Icon from './icon.png';
import Data from './data.xml';

function component() {
    var element = document.createElement('div');

    // Lodash, now imported by this script
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    element.classList.add('hello');

    // 将图像添加到我们现有的 div
    var myIcon = new Image();
    myIcon.src = Icon;

    element.appendChild(myIcon);

    console.log(Data);

    return element;
}

document.body.appendChild(component());

进行构建:

npm run dev

当你打开 index.html 并查看开发者工具中的控制台,你应该能够看到你导入的数据被打印在了上面!

全局资源

上述所有内容中最出色之处是,以这种方式加载资源,你可以以更直观的方式将模块和资源组合在一起。无需依赖于含有全部资源的 /assets 目录,而是将资源与代码组合在一起。例如,类似这样的结构会非常有用:

- |- /assets
+ |– /components
+ |  |– /my-component
+ |  |  |– index.jsx
+ |  |  |– index.css
+ |  |  |– icon.svg
+ |  |  |– img.png

回退处理

对于接下来的指南,我们无需使用本指南中所有用到的资源,因此我们会进行一些清理工作,以便为下一部分指南中的管理输出章节做好准备:

对于 src 目录,只保留 index.js 文件,其他的都删除。

webpack.config.js 文件,回退到:

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },

};

src/index.js 文件,回退到:

import _ from 'lodash';

function component() {
    var element = document.createElement('div');

    element.innerHTML = _.join(['Hello', 'webpack'], ' ');

    return element;
}

document.body.appendChild(component());

下面,进入管理输出章节。

管理输出

到目前为止,我们在 index.html 文件中手动引入所有资源,然而随着应用程序增长,并且一旦开始对文件名使用哈希(hash)]并输出多个 bundle,手动地对 index.html 文件进行管理,一切就会变得困难起来。然而,可以通过一些插件,会使这个过程更容易操控。

预先准备

首先,新增 src/print.js 文件,内容如下:

export default function printMe() {
  console.log('I get called from print.js!');
}

并且在 src/index.js 文件中使用这个函数:

import _ from 'lodash';
import printMe from './print.js';

function component() {
    var element = document.createElement('div');
    var btn = document.createElement('button');

    element.innerHTML = _.join(['Hello', 'webpack'], ' ');

    btn.innerHTML = 'Click me and check the console!';
    btn.onclick = printMe;

    element.appendChild(btn);

    return element;
}

document.body.appendChild(component());

更新 dist/index.html 文件,来为 webpack 分离入口做好准备:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>管理输出</title>
    <script src="./print.bundle.js"></script>
</head>
<body>
<script src="./app.bundle.js"></script>
</body>
</html>

现在调整配置。我们将在 entry 添加 src/print.js 作为新的入口起点(print),然后修改 output,以便根据入口起点名称动态生成 bundle 名称:

webpack.config.js

const path = require('path');

module.exports = {
    entry: {
        app: './src/index.js',
        print: './src/print.js'
    },
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    }
};

执行命令:

npm run dev

可以看到,webpack 输出了 print.bundle.js 和 app.bundle.js 文件,这也和我们在 index.html 文件中指定的文件名称相对应。

但是,如果我们更改了一个入口起点的名称,甚至添加了一个新的名称,会发生什么?我们的 index.html 文件仍然会引用旧的名字。这样就会产生问题,可以用 HtmlWebpackPlugin 插件来解决。

设定 HtmlWebpackPlugin

首先,安装 html-webpack-plugin 。

npm install --save-dev html-webpack-plugin

调整 webpack.config.js 文件:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: {
        app: './src/index.js',
        print: './src/print.js'
    },
    plugins: [
        new HtmlWebpackPlugin({
            title: 'Output Management'
        })
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    }
};

在我们构建之前,你应该了解,虽然在 dist/ 文件夹我们已经有 index.html 这个文件,然而 HtmlWebpackPlugin 还是会默认生成 index.html 文件。这就是说,它会用新生成的 index.html 文件,把我们的原来的替换。

进行构建:

npm run dev

如果你在代码编辑器中将 index.html 打开,你就会看到 HtmlWebpackPlugin 创建了一个全新的文件,所有的 bundle 会自动添加到 html 中。

也就是说,如果 webpack.config.js 文件中入口的某个键名改变了(比如,app 变为 apm),新生成的 index.html 文件中的 bundle 的名称也会随之自动改变。

清理 /dist 文件夹

可以发现,dist 文件夹相当杂乱。webpack 会生成文件,然后将这些文件放置在 /dist 文件夹中,但是 webpack 无法追踪到哪些文件是实际在项目中用到的。

通常,在每次构建前清理 /dist 文件夹,是比较推荐的做法。

clean-webpack-plugin 是一个比较普及的管理插件,可以通过它来清理文件夹。

首先,安装 clean-webpack-plugin 插件。

npm install clean-webpack-plugin --save-dev

然后,修改 webpack.config.js 配置文件。

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
    entry: {
        app: './src/index.js',
        print: './src/print.js'
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            title: 'Output Management'
        })
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    }
};

现在执行 npm run dev,再检查 /dist 文件夹。如果一切顺利,你现在应该不会再看到旧的文件,只有构建后生成的新文件。

结论

现在,你已经了解如何向 HTML 动态添加 bundle和如何清理文件夹。接着,进入下一章节——开发。

发布了378 篇原创文章 · 获赞 589 · 访问量 108万+

猜你喜欢

转载自blog.csdn.net/lamp_yang_3533/article/details/100110074