Skip to content

Files

Latest commit

author
zherong.czr
Apr 24, 2017
9e3f728 · Apr 24, 2017

History

History

part1

Webpack 初学者教学课程 Part 1 - Webpack 简介 ⚡

对于像我这样的人来说,第一次接触到 webpack 是像是这些 repository:

虽然这些 repository 放在一起很棒,但它们不一定是最好的学习工具。 像我的情况,我试着了解发生了那些事情,但还是很困惑,所以我决定不从这些大量而且分散的资源来理解。

我希望这个教学课程可以让 webpack 更容易的学习。

需求

至少希望你了解基本的 node.js 和 npm。

贡献

我很乐意接受任何所有的贡献或是修正。如果你有任何问题,可以将这些问题发成 issue。如果我有错误的话,请将问题指出。最后,如果你觉得我漏了些什么,或者可以将某些部分解释的更好,留下一个 issue 或者是发送 Pull Request。

目录

为什么要 Webpack?

因为每个 react 或 redux 教学课程都假设你知道什么是 webpack。:cry:

以下这些是更现实的原因,你可能会需要使用 webpack。

你可以:

  • 将你的 js 文件 Bundle 变成单一的文件
  • 在你的前端代码中使用 npm packages
  • 撰写 JavaScript ES6 或 ES7(需要通过 babel 来帮助)
  • Minify 或优化代码
  • 将 LESS 或 SCSS 转换成 CSS
  • 使用 HMR(Hot Module Replacement)
  • 包含任何类型的文件到你的 JavaScript
  • 更多进阶的东西,暂时不介绍
为什么我需要这些功能?
  • Bundle JS 文件 - 让你可以撰写模块化的 JavaScript,但是你不需要 include 每个 JavaScript <script> 的文件(如果你需要多个 JavaScript 文件可以通过配置来完成)。

  • 在你的前端代码中使用 npm packages - npm 在 internet 上是一个大型的 open source 生态系统。可以储存或发佈你的代码,你可以到 npm 看一看,可能包含你想要的前端套件。

  • ES6 和 ES7 - 加入一些 JavaScript 的新功能,让撰写代码可以更容易而且更强大,请看这里的介绍

  • Minify 或优化代码 - 减少你的文件大小,好处包括像是更快的将页面载入。

  • 将 LESS 或 SCSS 转换成 CSS - 使用更好的方式来撰写 CSS, 如果你不熟悉的话,这里有一些介绍

  • 使用 HMR - 增加开发速度。每当你储存代码的时候,它可以注入到网页,而不需将网页刷新。如果当编辑你的代码,你需要维护页面的状态,这是非常方便的。

  • 包含任何类型的文件到你的 JavaScript - 减少对其他 build 工具的需要,让你可以通过程序的方式修改或使用这些文件。

基础

安装

你需要全域安装来使用 webpack 大部分的功能:

npm install -g webpack

然而 webpack 有些功能,像是优化的 plugins,需要你将它安装在本机。像这种情况下你需要:

npm install --save-dev webpack

命令

如果要执行 webpack:

webpack

如果你想要 webpack 在你每次变更储存文件后自动执行 build :

webpack --watch

如果你想要使用自订的 webpack 配置:

webpack --config myconfig.js

Bundling

范例一

Official Dependency Tree

Webpack 简称为模块整合工具。如果你想要深入的话,可以拜访 JavaScript Modules: A Beginner’s GuideJavaScript Modules Part 2: Module Bundling 这两篇优秀的解释文章:

我们要保持它的简单,webpack 运作的方式是通过指定一个单一文件作为你的进入点。 这个文件会是 tree 的 root。然后你每次 require 一个文件从其他文件并把它加入到 tree。当你执行 webpack,所有的文件和 module 都会被 bundle 成一个文件。

这里是一个简单的范例:

Dependency Tree

根据这样的情况,你可以有这样的目录:

MyDirectory
|- index.js
|- UIStuff.js
|- APIStuff.js
|- styles.css
|- extraFile.js

这些可能是你文件的内容:

// index.js
require('./styles.css')
require('./UIStuff.js')
require('./APIStuff.js')

// UIStuff.js
var React = require('React')
React.createClass({
  // stuff
})

// APIStuff.js
var fetch = require('fetch') // fetch polyfill
fetch('https://google.com')
/* styles.css */
body {
  background-color: rgb(200, 56, 97);
}

当你执行 webpack,你会得到一个这个 tree 的 bundle 内容,虽然 extraFile.js 也是在相同的目录中,但它不是被 bundle 的一部份,因为它在 index.js 没有被 require

bundle.js 看起来会像:

// contents of styles.css
// contents of UIStuff.js + React
// contents of APIStuff.js + fetch

被 bundle 的这些文件是你明确所 require 进来的文件。

Loaders

你可能会注意到,我在上方的范例做了一些奇怪的事情。我在 JavaScript 文件中 require 一个 css 文件。

关于 webpack 真的很酷,有趣的事情是,你可以 require 其他不只是 JavaScript 的文件。

在 webpack 这些东西我们称为 loader。使用这些 loader,你可以 require 任何 .css.png.html 档。

例如在上图我有:

// index.js
require('./styles.css')

如果在我的 webpack 配置中,inclue style-loadercss-loader,这是可行的,还可以实际应用 CSS 到我的网页。

你可以在 webpack 使用多个 loader,这里只是一个单一的例子。

Plugins

Plugin,顾名思义就是替 webpack 增加额外的功能。其中常使用到的一个 plugin 是 UglifyJsPlugin,它可以 minify 你的 JavaScript 代码。我们之后会介绍如何使用。

你的 webpack 配置文件

Webpack 没办法直接使用,需要通过你的需求来做配置。为了做到这一点,你需要建立一个文件叫做:

webpack.config.js

预设情况下,webpack 会去识别这个档名。如果你选择使用不同的档名,你需要加入 --config 来指定你的文件名称。

一个简单的范例

范例二

你的目录结构像是这样:

MyDirectory
|- dist
|- src
   |- index.js
|- webpack.config.js

然后这是一个非常简易的 webpack 配置:

// webpack.config.js
var path = require('path')

module.exports = {
  entry: ['./src/index'], // 在 index 文件后的 .js 副档名是可选的
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  }
}

我们一个一个复习这些属性:

  • entry - 这是你的 bundle 的进入点,我们曾在前面 bundling 的部分讨论过它。entry 是一个阵列,根据你的需求,webpack 允许可以有多个进入点,来产生多个 bundle 文件。

  • output - 宣告 webpack 输出的形式。

    • path - 存放 bundle 文件的位置。
    • filename - bundle 文件名称。

根据上面的配置,当你执行 webpack,会在你的 dist 资料夹建立一个叫做 bundle.js 的文件。

介绍 Plugins

范例三

想像一下,你使用 webpack 将你的文件 bundle 在一起,然后你发现到 bundle 后的结果是 900KB。这是个问题,但是你可以通过 minify 你的 bundle 文件来做改善。要做到这一点,你需要使用一个我在前面稍早提到的 UglifyJsPlugin plugin。

此外,你需要在本机安装 webpack 才能实际的去使用这个 plugin。

npm install --save-dev webpack

现在你可以 require webpack 并 minify 你的代码。

// webpack.config.js
var path = require('path')
var webpack = require('webpack')

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

  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      compressor: {
        warnings: false,
      },
    })
  ]
}

我们一个一个复习这些属性:

如此一来,当我们执行 webpackUglifyJsPlugin 通过像是移除所有空白等处理,可以将你的文件减少至 200KB。

你也可以加入 OccurrenceOrderPlugin

根据调用次数指定 module 和 chunk 的 id。越常用的 id 较小(短)。这使得 id 可以预测,可以减少文件的大小,并是推荐的方法。

老实说,我不太确定底层的机制是如何工作的,但在根据目前 webpack2 beta 的预设情况下,所以我将它包含在内。

// webpack.config.js
var path = require('path')
var webpack = require('webpack')

module.exports = {
  entry: ['./src/index'],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      compressor: {
        warnings: false,
      },
    }),
    new webpack.optimize.OccurrenceOrderPlugin()
  ]
}

所以现在我们写了一个配置让我们可以 minify 和 bundle 我们的 JavaScript。这个 bundle 文件可以被复制并贴到其他的专桉目录中,放入 <script> 就可以使用。如果你只需要瞭解怎么用 webpack 处理 只有 JavaScript 的基本情况,你可以直接跳到结论

一个更完整的范例

此外,比起单单使用 JavaScript,使用 webpack 可以做的更多,你可以避免复制、贴上并通过 webpack 管理你的整个专桉。

在下面的部份中,我们要使用 webpack 建立一个非常简单的网站。如果你想要跟着这个范例,建立一个像下方的目录结构:

MyDirectory
|- dist
|- src
   |- index.js
   |- index.html
   |- styles.css
|- package.json
|- webpack.config.js

内容

  1. 介绍 Loaders - 我们将会加入 loader,这可以让我 bundle 加入的 CSS。
  2. 加入更多的 Plugins - 我们加入一个 plugin 来帮助我们建立和使用一个 HTML 文件。
  3. 开发服务器 - 我们会将 webpack 配置文件分为 developmentproduction 两种版本,然后使用 webpack-dev-server 来查看我们的网站并启用 HMR。
  4. 开始撰写程序 - 我们来实际写一些 JavaScript。

介绍 Loaders

范例四

在稍早前面的教学课程中我提到了 loaders。这些代码来帮助我们 require 非 JavaScript 的文件。在这种情况下,我们将需要 style-loadercss-loader。首先我们需要安装这些 loader:

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

现在安装完后,我们可以调整我们的 webpack 配置来引入 css-loader

// webpack.config.js
var path = require('path')
var webpack = require('webpack')

module.exports = {
  entry: ['./src/index'],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      compressor: {
        warnings: false,
      },
    }),
    new webpack.optimize.OccurrenceOrderPlugin()
  ],
  module: {
    loaders: [{
      test: /\.css$/,
      loaders: ['style', 'css']
    }]
  }
}

我们一个一个查看这些新属性:

  • module - 配置你的文件选项。
    • loaders - 我们为应用程序所指定的一个 loader 阵列。
      • test - 一个正规表达式,用来找出要套用 loader 的文件。
      • loaders - 指定哪些 loader 是用于匹配前述 test (正规表达式)的文件。

这个时候你执行 webpack,如果你 require 的文件结尾是 .css,我们会使用 stylecss loader,将 CSS 加入到 bundle。

如果我们没有 loaders,我们会得到像是这样的错误:

ERROR in ./test.css
Module parse failed: /Users/Developer/workspace/tutorials/webpack/part1/example1/test.css
Line 1: Unexpected token {
You may need an appropriate loader to handle this file type.

可选项目

如果你想要使用 SCSS 而不是 CSS 你需要执行:

npm install --save-dev sass-loader node-sass webpack

然后你的 loader 必须修改成:

{
  test: /\.scss$/,
  loaders: ["style", "css", "sass"]
}

处理 LESS 也类似于这个方式。

要知道这些需要被指定的 loader 是有顺序的,这是一个很重要部分。在上面的范例,sass loader 是第一个应用在你的 .scss 文件,然后是 css loader,最后是 style loader。你可以看到,这些 loader 的应用模式是由右到左。

加入更多的 Plugins

范例五

现在我们的网站已经有了样式的基本架构,我们还需要一个实际的页面来套用这些样式。

我们通过 html-webpack-plugin 来做,它让我们可以产生一个 HTML 页面,或是使用现有的页面。我们将使用一个目前现有的 index.html

首先我们需要安装 plugin:

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

然后在我们的 webpack 配置加入:

// webpack.config.js
var path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: ['./src/index'],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      compressor: {
        warnings: false,
      },
    }),
    new webpack.optimize.OccurrenceOrderPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  module: {
    loaders: [{
      test: /\.css$/,
      loaders: ['style', 'css']
    }]
  }
}

这个时候当你执行 webpack,因为我们指定了一个搭配 ./src/index.html template 的 HtmlWebpackPlugin ,它会产生一个文件叫做 index.html 在我们的 dist 资料夹,而网页的内容是 ./src/index.html

index.html 作为 template 如果是空的就没意义了,现在是个好时机我们可以填入一些元素进去。

<html>
<head>
  <title>Webpack Tutorial</title>
</head>
<body>
  <h1>Very Website</h1>
  <section id="color"></section>
  <button id="button">Such Button</button>
</body>
</html>

注意到我们没有放入一个 bundle.js<script> 标籤到我们的 HTML。实际上 plugin 会自动的帮你处理。如果你放入 script,到头来你会载入两次相同的代码。

而让我们加入一些基本的样式在 styles.css

h1 {
  color: rgb(114, 191, 190);
  text-align: center;
}

#color {
  width: 300px;
  height: 300px;
  margin: 0 auto;
}

button {
  cursor: pointer;
  display: block;
  width: 100px;
  outline: 0;
  border: 0;
  margin: 20px auto;
}

开发服务器

范例六

现在我们想要实际在浏览器看到我们的网站,这就需要一个 web 服务器来跑我们的代码。webpack 自带了方便的 webpack-dev-server,你需要在本机和全域安装。

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

dev server 可以在浏览器看到你的网站外观以及可以更快速的开发,是一个相当有用的资源。预设情况下你可以拜访 http://localhost:8080。不幸的是,像是 hot reloading 的功能并不是内建的,还需要一些其他的配置。

这里是 webpack 配置一个很棒的分离点,你可以将它分成用于「development」以及用于「production」。因为我们在这个教学课程中将尽量保持简单,所以两个配置之间不会有非常大的不同,不过这是 webpack 极度可设置性的一个入门。我们将两个配置命名为 webpack.config.dev.jswebpack.config.prod.js

// webpack.config.dev.js
var path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  devtool: 'cheap-eval-source-map',
  entry: [
    'webpack-dev-server/client?http://localhost:8080',
    'webpack/hot/dev-server',
    './src/index'
  ],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  module: {
    loaders: [{
      test: /\.css$/,
      loaders: ['style', 'css']
    }]
  },
  devServer: {
    contentBase: './dist',
    hot: true
  }
}

改变

  1. dev 配置省略了优化,因为当你不断的 rebuild 时,它们是不必要的。所以拿掉了 webpack.optimize plugins。

  2. dev 配置需要对 dev server 做必要的配置,你可以到这里了解更多。

总结:

  • entry: 两个新的进入点将服务器连结到浏览器,方便 HMR。
  • devServer
    • contentBase: 服务的文件来自哪裡。
    • hot: 启用 HMR。

prod 配置不需要改变太多:

// webpack.config.prod.js
var path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  devtool: 'source-map',
  entry: ['./src/index'],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      compressor: {
        warnings: false,
      },
    }),
    new webpack.optimize.OccurrenceOrderPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  module: {
    loaders: [{
      test: /\.css$/,
      loaders: ['style', 'css']
    }]
  }
}

我也加入一个全新的属性在 dev 和 prod 配置:

  • devtool - 这是协助 debug 的工具。基本上,当你得到一个错误,它会帮助你找到哪裡发生了错误,像是 chrome developer console。source-mapcheap-eval-source-map 之间的差异从文件说明有点难解释。我可以肯定的是,source-map 是用于 productioncheap-eval-source-map 是用于 development

如果要执行 dev server,我们可以执行:

webpack-dev-server --config webpack.config.dev.js

如果我们要 build production 的代码,我们可以执行:

webpack --config webpack.config.prod.js

如果想要让这些指令使用的更容易,我们可以到 package.json 来配置简单的 script。

我们加入 scripts 属性到配置:

// package.json
{
  //...
  "scripts": {
    "build": "webpack --config webpack.config.prod.js",
    "dev"  : "webpack-dev-server --config webpack.config.dev.js"
  }
  //...
}

我们可以执行这些指令:

npm run build
npm run dev

你现在可以通过 npm run dev,并导到 http://localhost:8080 看到你的网站。

备注: 当我正在测试这个部份时,我了解到,当我修改 index.html 文件时,服务器不能 hot reload。解决这个问题的方法在 html-reload。这里涵盖了一些 webpack 配置选项的有用信息,我推荐你可以看一下,但是我把它分开了,因为我觉得会因为这个不太重要的原因,这会延长这个教学课程。

开始撰写程序

范例七

大多数的人似乎会慌乱的原因是因为:webpack 事实上需要通过这些取得的进入点来撰写 JavaScript;然而我们现在已经到达了这个教学课程最高潮的部分。

如果你还没准备好:执行 npm run dev,以及导到 http://localhost:8080。配置 dev server 是不是可以 hot reload。在你每次储存你专桉所编辑的任何一个文件部份时,浏览器将会重新载入来显示你的修改。

我们也需要 npm package,为了来示范如何在前端使用它们。

npm install --save pleasejs

PleaseJS 是一个随机色彩的产生器,其中我们需要在按钮中加入 hook 来改变我们的 div 颜色。

// index.js

// 接受 hot module reloading
if (module.hot) {
  module.hot.accept()
}

require('./styles.css') // 网页现在有了样式
var Please = require('pleasejs')
var div = document.getElementById('color')
var button = document.getElementById('button')

function changeColor() {
  div.style.backgroundColor = Please.make_color()
}

button.addEventListener('click', changeColor)

有趣的是,为了让 Hot Module Replacement 可以执行,你需要加入下面的代码:

if (module.hot) {
  module.hot.accept()
}

在一个 module 或是它的父 module。

然后我们就完成了!

备注: 你可能已经注意到在你的 css 被使用之前有些 delay,或许事实上你讨厌将你的 css 放入到 JavaScript 文件中。我留了另一个范例:css-extract,描述如何将你的 CSS 放在不同的文件。

结论

我希望这些是有帮助的。

首先 Webpack 最重要的它是一个模块整合工具。它是一个高度模块化的工具,事实上,它并不是被限于在 ES6 和 React。

现在考虑到:

  • Part 2 将解决使用 Webpack 通过 Babel 将 ES6 转换到 ES5。
  • Part 3 将解决使用 Webpack 和 React + Babel。

因为这是这常见的例子。

反思

恭喜!你已经让你个按钮去改变你的 div 的颜色!webpack 是不是很棒?

没错是的!但是,如果你所做的事情只是让按钮去改变 div 的颜色,它可能不值得你去写像是这样的配置。如果你想这么做的话,你可能会感到...疲累。:anguished: