本文基于《入门Webpack,看这篇就够了》修改而来
原文详见:http://www.jianshu.com/p/42e11515c10f
关于webpack的介绍,这里不赘述。
直接上手!
开始使用Webpack
安装
- Webpack可以使用npm安装
//全局安装
npm install -g webpack
- 新建一个空的练习文件夹(此处命名为webpack sample project),在终端中转到该文件夹后执行下述指令
- 创建一个package.json文件,这是一个标准的npm说明文件,里面蕴含了丰富的信息,包括当前项目的依赖模块,自定义的脚本任务等等。在终端中使用 npm init 命令可以自动创建这个package.json文件
npm init
- 输入这个命令后,终端会问你一系列诸如项目名称,项目描述,作者等信息,不过不用担心,如果你不准备在npm中发布你的模块,这些问题的答案都不重要,回车默认即可
- package.json文件已经就绪,我们在本项目中安装Webpack作为依赖包
// 安装 Webpack
npm install --save-dev webpack
- 在webpack sample project 文件夹里面创建两个文件夹,app 文件夹和 build 文件夹
- app 文件夹用来存放原始数据和我们将写的 JavaScript 模块
- build 文件夹用来存放准备给浏览器读取的数据(包括使用 webpack 生成的打包后的 js 文件以及一个 index.html 文件)
在这里还需要创建三个文件,index.html 文件放在 build 文件夹中,两个 js 文件(Greeter.js 和 main.js)放在 app 文件夹中
index.html 文件只有最基础的 html 代码,它唯一的目的就是加载打包后的 js 文件(bundle.js)
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Webpack Sample Project</title>
</head>
<body>
<div id='root'>
</div>
<script src="bundle.js"></script>
</body>
</html>
- Greeter.js 只包括一个用来返回包含问候信息的 html 元素的函数。
// Greeter.js
module.exports = function() {
var greet = document.createElement('div');
greet.textContent = "Hi there and greetings!";
return greet;
};
- main.js 用来把 Greeter 模块返回的节点插入页面。
//main.js
var greeter = require('./Greeter.js');
document.getElementById('root').appendChild(greeter());
正式使用Webpack
- webpack 可以在终端中使用,其最基础的命令是
webpack {entry file/入口文件} {destination for bundled file/存放bundle.js的地方}
- 只需要指定一个入口文件,webpack将自动识别项目所依赖的其它文件,不过需要注意的是如果你的webpack没有进行全局安装,那么当你在终端中使用此命令时,需要额外指定其在node_modules中的地址,继续上面的例子,在终端中属于如下命令
//webpack 非全局安装的情况
node_modules/.bin/webpack app/main.js build/bundle.js
//webpack 全局安装的情况
webpack app/main.js build/bundle.js
- webpack 同时编译了 main.js 和 Greeter.js,现在打开 index.html,可以看到结果
通过配置文件来使用Webpack
- 当前练习文件夹的根目录下新建一个名为 webpack.config.js 的文件,并在其中进行最最简单的配置,如下所示,它包含入口文件路径和存放打包后文件的地方的路径
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/build",//打包后的文件存放的地方
filename: "bundle.js"//打包后输出文件的文件名
}
}
- 注:“__dirname”是node.js中的一个全局变量,它指向当前执行脚本所在的目录
- 现在如果你需要打包文件只需要在终端里你运行 webpack (非全局安装需使用node_modules/.bin/webpack) 命令就可以了,这条命令会自动参考 webpack.config.js 文件中的配置选项打包你的项目,输出结果
更快捷的执行打包任务
- npm 可以引导任务执行,对其进行配置后可以使用简单的 npm start 命令来代替这些繁琐的命令。在 package.json 中对 npm 的脚本部分进行相关设置即可,设置方法如下
{
...
"scripts": {
"start": "webpack --display-error-details --progress"
},
...
}
- 注:package.json中的脚本部分已经默认在命令前添加了node_modules/.bin路径,所以无论是全局还是局部安装的Webpack,你都不需要写前面那指明详细的路径了
- 现在只需要使用npm start就可以打包文件了
Webpack的强大功能
生成Source Maps(使调试更容易)
- 打包后的文件有时候你是不容易找到出错了的地方对应的源代码的位置的,Source Maps 就是来帮我们解决这个问题的。
- 通过简单的配置后,Webpack 在打包时可以为我们生成的 source maps,这为我们提供了一种对应编译文件和源文件的方法,使得编译后的代码可读性更高,也更容易调试
- 在 webpack 的配置文件中配置 source maps,需要配置 devtool,它有以下四种不同的配置选项,各具优缺点,描述如下
- 上述选项由上到下打包速度越来越快,不过同时也具有越来越多的负面作用,较快的构建速度的后果就是对打包后的文件的的执行有一定影响。
- 在学习阶段以及在小到中性的项目上,eval-source-map是一个很好的选项,不过记得只在开发阶段使用它
- cheap-module-eval-source-map方法构建速度更快,但是不利于调试,推荐在大型项目考虑da时间成本是使用
module.exports = {
devtool: 'eval-source-map',//配置生成Source Maps,选择合适的选项
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/build",
filename: "bundle.js"
}
}
使用webpack构建本地服务器
- 让你的浏览器监测你的代码的修改,并自动刷新修改后的结果,其实 Webpack 提供一个可选的本地开发服务器,这个本地服务器基于 node.js 构建,可以实现你想要的这些功能,不过它是一个单独的组件,在 webpack 中进行配置之前需要单独安装它作为项目依赖
npm install --save-dev webpack-dev-server
- devserver 作为 webpack 配置选项中的一项,具有以下配置选项
- 继续把这些命令加到webpack的配置文件中
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/build",
filename: "bundle.js"
},
devServer: {
contentBase: "./build",//本地服务器所加载的页面所在的目录
colors: true,//终端中输出结果为彩色
historyApiFallback: true,//不跳转
inline: true//实时刷新
}
}
Loaders
- Loaders 是 webpack 中最让人激动人心的功能之一了。通过使用不同的 loader,webpack 通过调用外部的脚本或工具可以对各种各样的格式的文件进行处理,比如说分析 JSON 文件并把它转换为 JavaScript 文件,或者说把下一代的 JS 文件(ES6,ES7)转换为现代浏览器可以识别的 JS 文件。或者说对 React 的开发而言,合适的 Loaders 可以把 React 的 JSX 文件转换为 JS 文件
Loaders 需要单独安装并且需要在 webpack.config.js 下的 modules 关键字下进行配置,Loaders 的配置选项包括以下几方面
test:一个匹配 loaders 所处理的文件的拓展名的正则表达式(必须)
loader:loader 的名称(必须)
include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
query:为 loaders 提供额外的设置选项(可选)- 把 Greeter.js 里的问候消息放在一个单独的 JSON 文件里,并通过合适的配置使 Greeter.js 可以读取该 JSON 文件的值,配置方法如下
//安装可以转换 JSON 的 loader
npm install --save-dev json-loader
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/build",
filename: "bundle.js"
},
module: {//在配置文件里添加 JSON loader
loaders: [
{
test: /\.json$/,
loader: "json-loader"
}
]
},
devServer: {
contentBase: "./build",
colors: true,
historyApiFallback: true,
inline: true
}
}
- 创建带有问候信息的 JSON 文件(命名为 config.json )
{
"greetText": "Hi there 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
Babe l其实是一个编译 JavaScript 的平台,它的强大之处表现在可以通过编译帮你达到以下目的:
下一代的 JavaScript 标准(ES6,ES7),这些标准目前并未被当前的浏览器完全的支持;
使用基于 JavaScript 进行了拓展的语言,比如 React 的 JSX
Babel的安装与配置
- Babel 其实是几个模块化的包,其核心功能位于称为 babel-core 的 npm 包中,不过 webpack 把它们整合在一起使用,但是对于每一个你需要的功能或拓展,你都需要安装单独的包(用得最多的是解析 Es6 的 babel-preset-es2015 包和解析 JSX 的 babel-preset-react包),我们先来一次性安装这些依赖包
// npm 一次性安装多个依赖模块,模块之间用空格隔开
npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-reac
- 在 webpack 中配置 Babel 的方法如下
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',//在 webpack 的 module 部分的 loaders 里进行配置即可
query: {
presets: ['es2015','react']
}
}
]
},
devServer: {
contentBase: "./build",
colors: true,
historyApiFallback: true,
inline: true
}
}
- webpack 的配置已经允许你使用 ES6 以及 JSX 的语法了。继续用上面的例子进行测试,不过这次我们会使用 React,记得先安装 React 和 React-DOM
npm install --save react react-dom
- 使用 ES6 的语法,更新 Greeter.js 并返回一个 React 组件
//Greeter.js
import React, {Component} from 'react';
import config from './config.json';
class Greeter extends Component{
render() {
return (
<div>
{config.greetText}
</div>
);
}
}
export default Greeter
- 使用 ES6 的模块定义和渲染 Greeter 模块
//main.js
import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';
render(<Greeter />, document.getElementById('root'));
Babel的配置选项
- Babel 其实可以完全在 webpack.config.js 中进行配置,但是考虑到 babel 具有非常多的配置选项,在单一的 webpack.config.js 文件中进行配置往往使得这个文件显得太复杂,因此一些开发者支持把 babel 的配置选项放在一个单独的名为 “.babelrc” 的配置文件中。我们现在的 babel 的配置并不算复杂,不过之后我们会再加一些东西,因此现在我们就提取出相关部分,分两个配置文件进行配置(webpack 会自动调用 .babelrc 里的 babel 配置选项)
// webpack.config.js
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'
}
]
},
devServer: {...} // Omitted for brevity
}
//.babelrc
{
"presets": ["react", "es2015"]
}
一切皆模块
- Webpack 有一个不可不说的优点,它把所有的文件都可以当做模块处理,包括你的 JavaScript 代码,也包括 CSS 和 fonts 以及图片等等,只有通过合适的 loaders,它们都可以被当做模块被处理
CSS
- webpack 提供两个工具处理样式表,css-loader 和 style-loader,二者处理的任务不同,css-loader 使你能够使用类似 @import 和 url(…) 的方法实现 require() 的功能,style-loader 将所有的计算后的样式加入页面中,二者组合在一起使你能够把样式表嵌入 webpack 打包后的 JS 文件中
//安装
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: {...}
}
- 注:感叹号的作用在于使同一文件能够使用不同类型的 loader
- 在app文件夹里创建一个名字为 “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;
}
h1, h2, h3, h4, h5, h6, p, ul {
margin: 0;
padding: 0;
}
- 为了让 webpack 能找到 ”main.css“ 文件,我们把它导入 ”main.js “ 中
//main.js
import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';
import './main.css'; //使用 require 导入 css 文件
render(<Greeter />, document.getElementById('root'));
- 通常情况下,css 会和 js 打包到同一个文件中,并不会打包为一个单独的 css 文件,不过通过合适的配置 webpack 也可以把 css 打包为单独的文件的。不过这也只是 webpack 把 css 当做模块而已
CSS module
- CSS modules 的技术就意在把 JS 的模块化思想带入 CSS 中来,通过 CSS 模块,所有的类名,动画名默认都只作用于当前模块
- Webpack 从一开始就对 CSS 模块化提供了支持,在 CSS loader 中进行配置后,你所需要做的一切就是把 ”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'//跟前面相比就在后面加上了 ?modules
}
]
},
devServer: {...}
}
- 创建一个 Greeter.css 文件
.root {
background-color: #eee;
padding: 10px;
border: 3px solid #ccc;
}
- 导入 .root 到 Greeter.js 中
import React, {Component} from 'react';
import config from './config.json';
import styles from './Greeter.css'; //导入
class Greeter extends Component{
render() {
return (
<div className={styles.root}> //添加类名
{config.greetText}
</div>
);
}
}
export default Greeter
CSS预处理器
- Sass 和 Less 之类的预处理器是对原生 CSS 的拓展,它们允许你使用类似于 variables, nesting, mixins, inheritance 等不存在于 CSS 中的特性来写 CSS,CSS 预处理器可以这些特殊类型的语句转化为浏览器可识别的 CSS 语句
在 webpack 里使用相关 loaders 进行配置就可以使用了,以下是常用的 CSS 处理 loaders
Less Loader
Sass Loader
Stylus Loader不过其实也存在一个 CSS 的处理平台 PostCSS,它可以帮助你的 CSS 实现更多的功能
- 举例来说如何使用 PostCSS,我们使用 PostCSS 来为 CSS 代码自动添加适应不同浏览器的 CSS 前缀
- 首先安装 postcss-loader 和 autoprefixer(自动添加前缀的插件)
npm install --save-dev postcss-loader autoprefixer
- 在 webpack 配置文件中进行设置,只需要新建一个 postcss 关键字,并在里面申明依赖的插件,如下,现在你写的 css 会自动根据 Can i use 里的数据添加不同前缀了
//webpack 配置文件
var webpack = require('webpack');
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!postcss-loader'
}
]
},
plugins: [
new webpack.LoaderOptionsPlugin({
options: {
postcss: [
require('autoprefixer') //调用 autoprefixer 插件
]
}
})
],
devServer: {...}
}
- 本文已经涉及到处理 JS 的 Babel 和处理 CSS 的 PostCSS,它们其实也是两个单独的平台,配合 Webpack 可以很好的发挥它们的作用
插件(Plugins)
- 插件(Plugins)是用来拓展 Webpack 功能的,它们会在整个构建过程中生效,执行相关的任务
- Loaders 和 Plugins 常常被弄混,但是他们其实是完全不同的东西,可以这么来说,loaders 是在打包构建过程中用来处理源文件的(JSX,Scss,Less..),一次处理一个,插件并不直接操作单个文件,它直接对整个构建过程其作用
使用插件的方法
- 要使用某个插件,我们需要通过 npm 安装它,然后要做的就是在 webpack 配置中的 plugins 关键字部分添加该插件的一个实例(plugins 是一个数组)
HtmlWebpackPlugin
- 这个插件的作用是依据一个简单的模板,帮你生成最终的 Html5 文件,这个文件中自动引用了你打包后的 JS 文件。每次编译都在文件名中插入一个不同的哈希值
安装
npm install --save-dev html-webpack-plugin
- 在 app 目录下,创建一个 Html 文件模板,这个模板包含 title 等其它你需要的元素,在编译过程中,本插件会依据此模板生成最终的 html 页面,会自动添加所依赖的 css, js,favicon 等文件,在本例中我们命名模板文件名称为 index.temp.html,模板源代码如下
<!-- index.temp.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Webpack Sample Project</title>
</head>
<body>
<div id='root'>
</div>
</body>
</html>
- 更新 webpack 的配置文件
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
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?modules!postcss-loader' }
]
},
plugins: [
new webpack.LoaderOptionsPlugin({
options: {
postcss: [
require('autoprefixer') //调用 autoprefixer 插件
]
}
}),
new HtmlWebpackPlugin({
template: __dirname + "/app/index.temp.html"//new 一个这个插件的实例,并传入相关的参数
})
],
devServer: {
colors: true,
historyApiFallback: true,
inline: true
}
}
Hot Module Replacement
- Hot Module Replacement(HMR)也是 webpack 里很有用的一个插件,它允许你在修改组件代码后,自动刷新实时预览修改后的效果
在 webpack 中实现 HMR 也很简单,只需要做两项配置
在 webpack 配置文件中添加 HMR 插件;
在 Webpack Dev Server 中添加“hot”参数;不过配置完这些后,JS 模块其实还是不能自动热加载的,还需要在你的 JS 模块中执行一个 Webpack 提供的 API 才能实现热加载,虽然这个 API 不难使用,但是如果是 React 模块,使用我们已经熟悉的 Babel 可以更方便的实现功能热加载
整理下我们的思路,具体实现方法如下
Babel 和 webpack 是独立的工具
二者可以一起工作
二者都可以通过插件拓展功能
HMR 是一个 webpack 插件,它让你能浏览器中实时观察模块修改后的效果,但是如果你想让它工作,需要对模块进行额外的配额;
Babel 有一个叫做 react-transform-hrm 的插件,可以在不对 React 模块进行额外的配置的前提下让 HMR 正常工作;
//webpack 中的配置
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
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?modules!postcss-loader' }
]
},
plugins: [
new webpack.LoaderOptionsPlugin({
options: {
postcss: [
require('autoprefixer') //调用 autoprefixer 插件
]
}
}),
new HtmlWebpackPlugin({
template: __dirname + "/app/index.temp.html"
}),
new webpack.HotModuleReplacementPlugin() //热加载插件
],
devServer: {
colors: true,
historyApiFallback: true,
inline: true,
hot: true
}
}
- 安装 react-transform-hmr
npm install --save-dev babel-plugin-react-transform react-transform-hmr
- 配置 Babel
//.babelrc
{
"presets": ["react", "es2015"],
"env": {
"development": {
"plugins": [["react-transform", {
"transforms": [{
"transform": "react-transform-hmr",
"imports": ["react"],
"locals": ["module"]
}]
}]]
}
}
}
优化插件
webpack 提供了一些在发布阶段非常有用的优化插件,它们大多来自于 webpack 社区,可以通过 npm 安装,通过以下插件可以完成产品发布阶段所需的功能
OccurenceOrderPlugin:为组件分配 ID,通过这个插件 webpack 可以分析和优先考虑使用最多的模块,并为它们分配最小的 ID
UglifyJsPlugin:压缩 JS 代码;
ExtractTextPlugin:分离 CSS 和 JS 文件
npm install --save-dev extract-text-webpack-plugin
- 在配置文件的 plugins 后引用它们
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
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: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader?modules!postcss-loader'
})
}
]
},
plugins: [
new webpack.LoaderOptionsPlugin({
options: {
postcss: [
require('autoprefixer') //调用 autoprefixer 插件
]
}
}),
new HtmlWebpackPlugin({
template: __dirname + "/app/index.temp.html"
}),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin("style.css")
],
devServer: {
historyApiFallback: true,
inline: true,
hot: true
}
}
缓存
- 缓存无处不在,使用缓存的最好方法是保证你的文件名和文件内容是匹配的(内容改变,名称相应改变)
- webpack 可以把一个哈希值添加到打包的文件名中,使用方法如下,添加特殊的字符串混合体([name], [id] and [hash])到输出文件名前
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/build",
filename: "[name]-[hash].js"
},
module: {
loaders: [
{
test: /\.json$/,
loader: "json-loader"
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader?modules!postcss-loader'
})
}
]
},
plugins: [
new webpack.LoaderOptionsPlugin({
options: {
postcss: [
require('autoprefixer') //调用 autoprefixer 插件
]
}
}),
new HtmlWebpackPlugin({
template: __dirname + "/app/index.temp.html"
}),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin("[name]-[hash].css")
],
devServer: {
historyApiFallback: true,
inline: true,
hot: true
}
}
- 这次整理,主要针对 Webpack 和 Webpack2 的不同之处进行调整