Webapck快速入门

什么是Webpack?

多年来,Web开发从具有少量资产和少量JavaScript的网页发展为具有复杂JavaScript和大依赖性树(依赖多个其他文件的文件)的全功能Web应用程序。为了应对日益复杂的社会问题,社区提出了不同的方法和实践,例如:

  • 在JavaScript中使用模块,使我们能够将程序分成几个文件。
  • JavaScript预处理器(允许你使用浏览器原生不支持的最新的CSS和JavaScript中的特性)和编译成JavaScript的语言(例如CoffeeScript)

但是,这些进步虽然非常有帮助,但却带来了开发过程中额外步骤的需要:我们需要将这些文件捆绑在一起并将其转换(浏览/编译)为浏览器可以理解的内容。这就是Webpack这样的工具所必需的。

Webpack是一个模块打包工具:可以分析你的项目结构,查找JavaScript模块和其他资源,将其打包并打包到浏览器。

Webpack、Grunt和Gulp都是如何构建的?

Webpack不同于自动构建或像Grunt和Gulp这样的工具,因为它本身不是一个构建工具,但它可以取代它们的优点。

Grunt和Gulp等工具通过查找与你的配置匹配的文件路径进行构建。在配置文件中,还需要指定应该运行的任务和步骤,以便转换,合并或压缩这些文件。


Grunt和Gulp

而Webpack是分析你的项目的整体结构。 给定一个开始的主文件,Webpack会查看所有项目的依赖关系(通过JavaScript中的require和import语句),使用加载器处理它们,并生成一个捆绑的JavaScript文件。


Webpack

Webpack的方法更快,更直接。 而且,正如你将在本章后面看到的,打开了捆绑不同文件类型的许多新的可能性。

开始使用Webpack

全局安装

$ npm install -g webpack

或局部安装

# cd webpack-demo
$ npm install --save-dev webpack

示例项目

# 1.创建一个空文件夹
$ mkdir webpack-demo && cd webpack-demo
# 2.创建一个package.json文件 - 一个标准的npm清单,它包含有关项目的各种信息,并让开发者指定依赖项(可以自动下载和安装)并定义脚本任务。
$ npm init
# 3.将webpack添加为项目依赖项,并使用以下命令进行安装
$ npm install --save-dev webpack
# 4.创建两个目录(app和public)和三个文件(Greeter.js, main.js, index.html)
$ mkdir app && cd app
$ touch Greeter.js
$ touch main.js
$ mkdir public && cd public
$ touch index.html

最后,项目结构如下图所示:


项目结构

index.html源码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Webpack Demo</title>
  </head>
  <body>
    <div id='root'>
    </div>
    <script src="bundle.js"></script>
  </body>
</html>

Greeter.js源码:

module.exports = function() {
  var greet = document.createElement('div');
  greet.textContent = "Hi there and greetings!";
  return greet;
};

main.js源码:

var greeter = require('./Greeter.js');
document.getElementById('root').appendChild(greeter());

运行构建

node_modules/.bin/webpack app/main.js public/bundle.js
输出

访问

配置Webpack(可选)

Webpack有许多不同的和高级的选项,允许使用加载器和插件来对加载的模块进行转换。尽管可以使用webpack和命令行中的所有选项,但是这个过程往往会变得缓慢且容易出错。一个更好的方法是定义一个配置文件 - 一个简单的JavaScript模块,然后在其中放置与构建相关的所有信息。

现在在示例项目中创建一个名为webpack.config.js的文件。Webpack配置文件必须有入口文件和bundle文件的输出目录:

$ touch webpack.config.js

webpack.config.js 源码:

module.exports = {
  entry:  __dirname + "/app/main.js",
  output: {
    path: __dirname + "/public",
    filename: "bundle.js"
  }
}

注意:“__dirname”是一个包含当前正在执行的脚本所在目录的名称的node.js全局变量。

现在你可以简单地在终端上运行“webpack”,而无需任何参数 - 因为现在有一个webpack.config文件,webpack命令将根据可用的配置构建你的应用程序。 命令的结果应该如下所示:

$ node_modules/.bin/webpack
输出

npm 启动(可选)

执行诸如“node_modules/.bin/webpack”之类的长命令容易出错。值得庆幸的是,npm可以作为一个任务运行者,可以通过npm start隐藏冗长的脚本。这可以通过设置一个脚本部分到package.json来实现,如下所示:

package.json

请注意,在package.json文件中配置的所有脚本已经在路径中具有“node_modules/.bin”文件夹,因此你不必使用完整路径显式调用所需的命令。

输出

配置Source Map

Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码。这无疑给开发者带来了很大方便。

将所有项目的JavaScript模块打包成一个(或几个)bundle文件以便在浏览器上使用时会带来很多好处,但一个明显的缺点是,当在浏览器中进行调试时你将无法在原始文件中找到映射。好在Webpack可以在绑定时生成源映射(source map) - 源映射提供了将绑定文件中的代码映射回其原始源文件的方法,使代码更易读,更易于在浏览器中调试。

要将Webpack配置为生成指向原始文件的源映射,请使用以下选项之一的“devtool”设置

devtool选项
  • source-map:在单独的文件中生成完整的全功能源映射。这个选项具有最好的源图质量,但是它确实减慢了构建过程。
  • cheap-module-source-map:在不带列映射的单独文件中生成源映射。剥离列映射有利于构建却不便于调试:浏览器开发人员工具只能指向原始源代码的行,而不能指向特定的列(或字符)。
  • eval-source-map:使用“eval”将源代码模块捆绑在一起,在同一个文件中嵌套完整的源映射。这个选项确实会生成一个全功能的源映射,且不会对构建时间产生很大的影响,但是在JavaScript执行中会带来性能和安全方面的缺陷。虽然在开发过程中使用这个选项是一个不错的选择,但是这个选项不应该在生产中使用。
  • cheap-module-eval-source-map:在构建期间生成源映射的最快方法。生成的源映射将使用相同的捆绑JavaScript文件进行内联,而不使用列映射。和前面的选项一样,JavaScript执行时间也有缺陷,所以这个选项不适合生成生产就绪包。

这些选项是从最慢的构建时间到最快的排序的。 学习和中小型项目中,“eval-source-map”是一个很好的选择。

module.exports = {
  devtool: 'eval-source-map',
  entry:  __dirname + "/app/main.js",
  output: {
    path: __dirname + "/public",
    filename: "bundle.js"
  }
}

本地服务webpack-dev-server

Webpack有一个可选的服务器用于本地开发。它是一个小型的node.js express应用程序,可以根据你的webpack配置提供服务和构建,并将其保存在内存中,在更改源文件时自动刷新浏览器。这是一个单独的npm模块,应该作为项目依赖项安装:

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

webpack dev服务器可以在webpack.config.js配置文件中的devserver键下配置。 配置设置包括:

module.exports = {
  devtool: 'eval-source-map',
  entry:  __dirname + "/app/main.js",
  output: {
    path: __dirname + "/public",
    filename: "bundle.js"
  },
  devServer: {
    contentBase: "./public",
    colors: true,
    historyApiFallback: true,
    inline: true
  } 
}

现在执行“webpack-dev-server”命令代替webpack来启动服务器:

node_modules/.bin/webpack-dev-server
输出

为方便起见,可以修改package.json中的start命令如下

package.json

“--progress”参数仅在命令行中可用。构建时会在终端中显示进度。

加载器Loaders

通过使用加载器,webpack可以通过外部脚本和工具对源文件进行预处理以应用各种变化和转换。例如:

  • 将JSON文件解析为普通JavaScript
  • 将ES6转换成当前浏览器可以理解的常规JavaScript。
  • React的JSX转换成普通的JavaScript。

加载器需要单独安装,并应在webpack.config.js中的module键下进行配置。其设置包括:

  • test:一个正则表达式,通过匹配文件扩展名获取加载器运行的文件(必需)。
  • loader:加载器的名称(必需)。
  • include / exclude:可选设置,手动设置加载器应显式添加或忽略的文件夹和文件。
  • query:查询设置可用于将其他选项传递给加载器。

首先安装Webpack的json加载器模块:

$ npm install --save-dev json-loader

然后修改配置文件:

module.exports = {
  devtool: 'eval-source-map',
  entry:  __dirname + "/app/main.js",
  output: {
    path: __dirname + "/public",
    filename: "bundle.js"
  },
  module: {
    loaders: [
      {
        test: /\.json$/,
        loader: "json-loader"
      }
    ]
  },
  devServer: {
    contentBase: "./public",
    colors: true,
    historyApiFallback: true,
    inline: true
  }
}

最后创建一个json文件

$ cd ./app && touch config.json

config.json:

{
  "greetText": "Hi vincent and greetings from JSON!"
}

Greeter.js:

var config = require('./config.json');
module.exports = function () {
  var greet = document.createElement('div');
  greet.textContent = config.greetText;
  return greet;
};

Babel

Babel是一个JavaScript编译和工具的平台。 通过它你可以:

  • 使用所有浏览器尚未完全支持的JavaScript(ES6 / ES2015,ES7 / ES2016等)的下一个版本。
  • 使用JavaScript语法扩展,如React的JSX。

安装和配置

Babel是模块化的,分布在不同的npm模块中。核心功能可以在“babel-core”npm包中找到,与webpack的集成可以通过npm包“babel-loader”获得,对于想要提供给代码的每种类型的功能和扩展,需要安装一个单独的包(最常见的是babel-preset-es2015和babel-preset-react,分别用于编译ES6和React的JSX)。

npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-stage-0 babel-preset-react
npm install --save react react-dom
module.exports = {
  devtool: 'eval-source-map',
  entry: __dirname + "/app/main.js",
  output: {
    path: __dirname + "/public",
    filename: "bundle.js"
  },
  module: {
    loaders: [
      {
        test: /\.json$/,
        loader: "json-loader"
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react', 'stage-0']
        }
      }
    ]
  },
  devServer: {
    contentBase: "./public",
    historyApiFallback: true,
    inline: true
  }
}

Greeter.js:

import React, { Component } from 'react';
import config from './config.json';
export default class Greeter extends Component {
  alertGreeting = () => {
    const greetings = config.greetText
    alert(`greetings:${greetings}`);
  }
  render() {
    return (
      <div onClick={this.alertGreeting}>
        {config.greetText}
      </div>
    )
  }
}

main.js:

import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';
render(<Greeter />, document.getElementById('root'));

Babel配置文件

由于在项目后期中Babel可能有很多配置设置选项和组合,在同一个文件中处理所有内容可能会很麻烦。许多开发人员选择创建一个单独的babel资源配置,即一个“.babelrc”文件。首先,从webpack.config.js文件中删除预设配置,仅保留基本的加载器设置:

module.exports = {
  devtool: 'eval-source-map',
  entry:  __dirname + "/app/main.js",
  output: {
    path: __dirname + "/public",
    filename: "bundle.js"
  },
  module: {
    loaders: [
      {
        test: /\.json$/,
        loader: "json-loader"
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  },
  devServer: {...} 
}  

.babelrc:

{
  "presets": [
    "es2015",
    "react",
    "stage-0"
  ]
}

Webpack可以将每一种文件都视为一个模块 - 使用合适的装载器,JavaScript代码,CSS,字体,都可以被视为模块。 Webpack可以通过所有依赖树追踪CSS中@import和URL值,然后构建,预处理和捆绑资源。

Stylesheets

Webpack提供了两个加载器来处理样式表:css-loader和style-loader。css-loader查找@import和url语句并解析它们时,style-loader将所有计算的样式规则添加到页面中。结合在一起,这些加载器使你能够将样式表嵌入到Webpack JavaScript包中。

安装

$ npm install --save-dev style-loader css-loader
module.exports = {
  devtool: 'eval-source-map',
  entry:  __dirname + "/app/main.js",
  output: {
    path: __dirname + "/build",
    filename: "bundle.js"
  },
  module: {
    loaders: [
      {
        test: /\.json$/,
        loader: "json-loader"
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        loader: 'style-loader!css-loader'
      }
    ]
  },
  devServer: {...}
}

感叹号(“!”)可用于加载程序配置,以将不同的加载器链接到相同的文件类型。

创建文件

$ touch main.css

main.css:

html {
  box-sizing: border-box;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
}
*, *:before, *:after {
  box-sizing: inherit;
}
body {
  margin: 0;
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
  color: brown;
}
h1, h2, h3, h4, h5, h6, p, ul {
  margin: 0;
  padding: 0;
}

main.js:

import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';
import './main.css';
render(<Greeter />, document.getElementById('root'));

默认情况下,你的CSS规则将与JavaScript文件捆绑在一起 - 它不会生成一个单独的CSS捆绑文件。

CSS 模块

通过使用CSS模块,所有类名称和动画名称都默认在本地范围内生效。Webpack从一开始就通过CSS加载器支持CSS模块,我们只需通过传递“modules”查询字符串启用此功能,并且可以将CSS类中的类名导出到本地范围的组件代码中。

module.exports = {
  devtool: 'eval-source-map',
  entry:  __dirname + "/app/main.js",
  output: {...},
  module: {
    loaders: [
      {
        test: /\.json$/,
        loader: "json-loader"
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        loader: 'style-loader!css-loader?modules'
      }
    ]
  },
  devServer: {...}
}

接下来,让我们创建一个CSS文件(带有专门用于Greeter组件的样式规则),并将这些规则导入到Greeter.js React模块中。

$ touch Greeter.css

Greeter.css:

.root {
  background-color: yellow;
  padding: 10px;
  border: 3px solid #ccc;
}

注意在上面的代码中,css类是如何被导入到一个变量(styles)中的,并被单独应用到一个JSX元素上。还要注意,任何具有自己独立CSS模块的其他组件也可以使用相同的类名称而不受干扰:即使是非常常见的样式名称,例如“root”,“header”,“footer” 在本地范围内安全使用。

结果

待续...