Webpack 入门:入口、输出、加载器和插件

Avatar of Jeremias Menichelli
Jeremias Menichelli 发表于

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

前端开发已转向模块化方法,从而提高了代码库的封装和结构。工具成为任何项目的重要组成部分,现在有很多选择。

Webpack 因其强大的功能和可扩展性在过去几年中获得了普及,但一些开发者发现其配置过程令人困惑且难以采用。

我们将逐步从一个空的配置文件开始,到一个简单但完整的项目捆绑设置。本文假设您对 CommonJS 表示法以及模块的工作原理有基本的了解。

概念

与大多数捆绑器不同,Webpack 背后的动机是收集所有依赖项(不仅是代码,还有其他资产)并生成依赖关系图。

起初,看到一个 `.js` 文件需要一个样式表,或者一个样式表检索一个修改过的图像就像它是一个模块一样,这可能看起来很奇怪,但这些允许 Webpack 理解捆绑包中包含的内容,并帮助您转换和优化它们。

安装

让我们首先添加我们将要使用的初始包

npm install webpack webpack-dev-server --save-dev

接下来,我们在项目的根目录中创建一个 `webpack.config.js` 文件,并在我们的 `package.json` 文件中添加两个脚本,用于本地开发和生产发布。

"scripts": {
  "start": "webpack-dev-server",
  "build": "webpack"
}

Webpack 命令将拾取我们刚刚创建的配置文件,除非我们指示其他操作。

入口

有很多方法可以指定我们的“入口点”,它将是我们依赖关系图的根源。

最简单的方法是传递一个字符串

var baseConfig = {
  entry: './src/index.js'
};

如果将来需要多个入口,我们也可以传递一个对象。

var baseConfig = {
  entry: {
    main: './src/index.js'
  }
};

我推荐最后一个,因为它随着项目的增长而具有更好的可扩展性。

输出

Webpack 中的输出是一个对象,它保存了我们的捆绑包和资产将要放置的路径,以及入口将采用的名称。

var path = require('path');

var baseConfig = {
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'main.js',
    path: path.resolve('./build')
  }
};

// export configuration
module.exports = baseConfig;

如果您使用对象定义 **入口**,而不是使用字符串硬编码输出文件名,则可以执行以下操作

output: {
  filename: '[name].js',
  path: path.resolve('./build')
}

这样,当添加新入口时,Webpack 将拾取其键来形成文件名。

仅使用这少量配置,我们就可以运行服务器并在本地使用 `npm start` 或 `npm run build` 进行开发,以捆绑我们的代码以供发布。通过了解项目的依赖关系,webpack-dev-server 将监视它们,并在检测到其中一个依赖关系发生更改时重新加载站点。

加载器

Webpack 的目标是处理我们所有的依赖项。

// index.js file
import helpers from '/helpers/main.js';

// Hey Webpack! I will need these styles:
import 'main.css';

什么?在 JavaScript 中需要样式表?是的!但是捆绑器仅准备开箱即用地处理 JavaScript 依赖项。这就是“加载器”登场的地方。

加载器提供了一种简单的方法来拦截我们的依赖项并在它们被捆绑之前对其进行预处理。

var baseConfig = {
  // ...
  module: {
    rules: [
      {
        test: /* RegEx */,
        use: [
          {
            loader: /* loader name */,
            query: /* optional config object */
          }
        ] 
      }
    ]
  }
};

为了使加载器工作,我们需要一个正则表达式来识别我们想要修改的文件,以及一个字符串或一个数组,其中包含我们想要使用的加载器。

样式

为了允许 Webpack 在需要时处理我们的样式,我们将安装 **css** 和 **style** 加载器。

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

**css-loader** 将样式解释为依赖项,而 **style-loader** 将在捆绑包加载时自动在页面上包含一个 `<style>` 标签。

var baseConfig = {
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: '[name].js',
    path: path.resolve('./build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader' },
          { loader: 'css-loader' }
        ]
      }
    ]
  }
};

在此示例中,`main.css` 将首先通过 **css-loader**,然后通过 **style-loader**。

预处理器

添加对 LESS 或任何其他预处理器的支持就像安装相应的加载器并将其添加到规则一样简单。

rules: [
  {
    test: /\.less$/,
    use: [
      { loader: 'style-loader' },
      { loader: 'css-loader' },
      { loader: 'less-loader' }
    ]
  }
]

转译

JavaScript 也可以通过加载器进行转换。一个例子是使用 Babel 加载器来转译我们的脚本。

rules: [
  {
    test: /\.js$/,
    use: [
      { loader: 'babel-loader' }
    ]
  }
]

图像

Webpack 具有一个很棒的功能,它可以检测样式表中的 `url()` 语句,并让加载器对图像文件和 url 本身应用更改。

// index.less file

@import 'less/vars';

body {
  background-color: @background-color;
  color: @text-color;
}

.logo {
  background-image: url('./images/logo.svg');
}

通过添加一个规则,我们可以应用 **file-loader** 来简单地复制文件,或者使用 **url-loader**,后者将图像内联为 base64 字符串,除非它超过字节限制,在这种情况下,它将用相对路径替换 url 语句,并将文件复制到输出位置。

{
  test: /\.svg$/,
  use: [
    {
       loader: 'url-loader',
       query: { limit : 10000 }
    }
  ]
}

加载器可以通过传递一个包含选项的 `query` 对象来配置,就像这里我们配置加载器在文件大小超过 10Kb 之前内联文件一样。

通过这种方式管理我们的构建过程,我们将只包含必要的资源,而不是移动一个包含大量可能或可能不会在我们的项目中使用的文件的假设资产文件夹。

如果您使用 React 或类似的库,您可以使用 **svg-inline-loader** 在组件中需要 `.svg` 文件。

插件

Webpack 包含默认行为来捆绑大多数类型的资源。当加载器不足时,我们可以使用插件来修改或添加 Webpack 的功能。

例如,Webpack 默认情况下将我们的样式包含在我们的捆绑包中,但我们可以通过引入插件来更改这一点。

提取资产

插件的常见用途是提取生成的样式表并像平时一样使用 `<link>` 标签加载它。

var ExtractTextPlugin = require('extract-text-webpack-plugin');

var lessRules = {
  use: [
    { loader: 'css-loader' },
    { loader: 'less-loader' }
  ]
};

var baseConfig = {
  // ...
  module: {
    rules: [
      // ...
      { test: /\.less$/, use: ExtractTextPlugin.extract(lessRules) }
    ]
  },
  plugins: [
    new ExtractTextPlugin('main.css')
  ]
};

生成 `index.html` 文件

当构建单页面应用时,我们通常需要一个.html文件来提供服务。

HtmlWebpackPlugin 会自动创建一个`index.html`文件,并为每个生成的 bundle 添加 script 标签。它还支持模板语法并且高度可配置。

var HTMLWebpackPlugin = require('html-webpack-plugin');

var baseConfig = {
  // ...
  plugins: [
    new HTMLWebpackPlugin()
  ]
};

构建生产环境

定义环境

许多库会引入在开发时有用的警告,但在我们的生产 bundle 中没有用处,并且会增加 bundle 的大小。

Webpack 带有一个内置插件,可以在 bundle 内部设置全局常量。

var ENV = process.env.NODE_ENV;

var baseConfig = {
  // ...
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(ENV)
    })
  ]
};

现在我们需要在我们的命令中指定环境

"scripts": {
  "start": "NODE_ENV=development webpack-dev-server",
  "build": "NODE_ENV=production webpack"
}

process.env.NODE_ENV 将被替换为一个字符串,允许压缩器消除无法访问的开发代码分支。

这对于在代码库中引入团队的警告非常有用,并且它们不会进入生产环境。

if (process.env.NODE_ENV === 'development') {
    console.warn('This warning will dissapear on production build!');
  }

压缩

在生产环境中,我们需要为用户提供尽可能快的产品。通过缩小我们的代码并删除不必要的字符,这减少了 bundle 的大小并提高了加载时间。

最流行的工具之一是 UglifyJS,Webpack 带有一个内置插件来传递我们的代码。

// webpack.config.js file
var ENV = process.env.NODE_ENV;

var baseConfig = {
  // ...
  plugins: []
};

if (ENV === 'production') {
  baseConfig.plugins.push(new webpack.optimize.UglifyJsPlugin());
}

总结

Webpack 配置文件非常有用,文件复杂度取决于你的需求。注意将其组织好,因为随着项目增长,它们可能会变得难以管理。

在本文中,我们从一个空白的配置文件开始,最终得到一个基本设置,它允许你在本地开发并发布生产代码。Webpack 还有更多内容可以探索,但这些关键部分和概念可以帮助你更好地了解它。

如果你想更深入地了解,我推荐 Webpack 官方文档,该文档已针对其第二个大版本进行了更新和改进。