在GitHub上发现的学习webpack和react的很好的文章,原文链接
通过看前两篇文章你可能对Webpack有了一些了解,它是一个模块购建工具,可以打包你的文件.
听起来容易,实际上却不简单,我们都不想自已处理所有细节,这正是webpack要做的,接下来,将配置我们Webpack的第一个工程,并运行它在开发模式.
建立工程
Webpack依赖于Node.js. 请确让你已装安装了它,并可以在你的命令行中运行npm
命令. 建立一个目录用来放置你的工程, 进入到目录中,运行npm init
, 并按提示填写信息,或者全部回车使用默任设置.
mkdir kanban_app
cd kanban_app
npm init -y # -y 直接使用默认的 *package.json*, 跳过手动设置
在你工程的目录下将会生成package.json文件,你可以打开它来进一步的配置你的工程.下面会主要使用npm这个工具,但是还是建议大家学一下package.json中的另种选项用法.
安装Git
略.
安装Webpack
下一步, 我们安装Webpack, 我们使用局部安装并把它保存为工程依赖,这有利于针对每个工程,维护Webpack的版本. 执行
npm i webpack --save-dev
npm维护由它安装的可执行包的目录,你能使用npm bin
命令显示这个具体的目录,很可能是.../node_modules/.bin
。试一下在命令行中运行node_modules/.bin/webpack
命令 或 node_modules\.bin\webpack
在windows。
之后你会看到打印出的有版本信息,和一些可用的选项。
kanban_app $ node_modules/.bin/webpack
webpack 1.12.12
Usage: https://webpack.github.io/docs/cli.html
Options:
--help, -h, -?
--config
--context
--entry
...
--display-cached-assets
--display-reasons, --verbose, -v
Output filename not configured.
Webpack使用全局安装(安装时指定-g或-global),而不是使用它安装为项目的依赖,这样你可以直接控制运行的版本,它不依赖于单独的工程,你可以在任何地方使用它。
我们使用--save
和--save-dev
区分应用环境依赖和开发环境依赖,前者将安装到package.json的dependencies
节点,而后者将写到devDependencies
节点。这种分离使得项目依赖关系更容易理解,最后打包时将不会打包devDependencies
所包含的包。
目录结构
现在的项目只有package.json看起来很low,我们将配置更多的具体的东西. 我们将实现一个小的web站点,并加载一些些JavaScript,然后使用Webpack进行构建. 要开始的话,请配置我们的目录结构如下.
- /app
- index.js
- component.js
- /build
- index.html
- package.json
- webpack.config.js
我们将使用Webpack,基于/app生成bundle.js,为了做的这一点,我们应该设置一些资源和webpack.config.js.
设置资源
我们喜欢Hello world,所以我们创建一个这个模块.
app/component.js
module.exports = function () {
var element = document.createElement('h1');
element.innerHTML = 'Hello world';
return element;
};
接下来,我们需要一个我们应用的入口点. 用require
加载我们的组件并渲染它到一个DOM:
app/index.js
var component = require('./component');
var app = document.createElement('div');
document.body.appendChild(app);
app.appendChild(component());
我们还需要一些HTML,用来加载我们上面的最终生成的bundle.js
build/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Kanban app</title>
</head>
<body>
<div id="app"></div>
<script src="./bundle.js"></script>
</body>
</html>
Webpack配置文件
我们需要告诉Webpack怎么处理我们刚刚建立的资源.这些需要在webpack.config.js中配置.
webpack.config.js
const path = require('path');
const PATHS = {
app: path.join(__dirname, 'app'),
build: path.join(__dirname, 'build')
};
module.exports = {
// Entry accepts a path or an object of entries. We'll be using the
// latter form given it's convenient with more complex configurations.
entry: {
app: PATHS.app
},
output: {
path: PATHS.build,
filename: 'bundle.js'
}
};
上面的entry
使用相对路径. 这个context字段,能用来配置绝对路径,考虑到很多地方需要绝对路径,我更喜欢使用绝对路径避免产生混淆.
我想使用path.join
,但path.resolve
是一个更好的选择.path.resolve
等介于通过cd命令进行导航. 而path.join
只是给你一个链接.详细信息请看 Node.js path API .
当我们调用 node_modules/.bin/webpack
, 将会看到如下输出信息:
Hash: e7dd4bf8a294b5c93141
Version: webpack 1.12.11
Time: 734ms
Asset Size Chunks Chunk Names
bundle.js 12 kB 0 [emitted] main
[0] ./app/index.js 169 bytes {0} [built]
[5] ./app/component.js 136 bytes {0} [built]
+ 4 hidden modules
它意思是构建你的输出目录,使用浏览器打开build/index.html
文件查看例子.
增加构建脚本
考虑到每次执行node_modules/.bin/webpack
有些麻烦,我们需要做些什么,通过npm和package.json控制程序运转.
package.json
...
"scripts": {
"build": "webpack"
},
...
可以通过执行npm run 的方式运行脚本.尝试调用npm run build
你会得到和之前相同的结果.
之所以"build": "webpack"
没有配全路径,是因为npm会附加node_modules/.bin这个路径到运行时路径,即相当于配置"build": "node_modules/.bin/webpack"
. 还有一总可能是使用全局安装Webpack,但这样是有潜在危险的,它不利于我们对每个工程进行版本控制.
还能做的更好一些, 通过Grunt或者Gulp运行任务,可以达到相同的操作,并且是跨平台的. 但我们现在只是菜鸟,让我们keep things simple,暂时不去考虑它们.
配置开发服务器 webpack-dev-server
通过构建脚本开发你的应用程序,让人提不起劲,Webpack提供了一个更好的开发方式,通过webpack-dev-server这个运行在内存中的服务器,你在开发应用时它会自动刷新你的内容.它很像LiveReload或者Browsersync
这些工具.
Webpack拥有这些工具最大的好处是模块热插拔(HMR), 总之,它提供了一总修补方式,不会完成刷新浏览器状态. 我们将会在学习React时详细讨论它.
你应该只是在开发时使用webpack-dev-server,如果是正式的应用考虑一个其它的成熟的解决方案,如Apache或Nginx.
要想开始webpack-dev-server,需要在跟目录下执行.
npm i webpack-dev-server --save-dev
就像上面, 我们在package.json中的scripts
定义一个入口点.考虑到index.html在build目录下面,我们配置webpack-dev-server
基于这个目录.改变我们的package.json.
package.json
"scripts": {
"build": "webpack",
"start": "webpack-dev-server --content-base build"
},
现在你执行npm run start或npm start,你应该会在命令行中看到这些信息:
> webpack-dev-server
http://localhost:8080/
webpack result is served from /
content is served from .../kanban_app/build
404s will fallback to /index.html
webpack: bundle is now VALID.
意思是,服务器已经运行起来了.如果在浏览器中打开http://localhost:8080/,你应该看到Hello World,如果你修改你的代码,你应该能在终端看到一些信息.问题是我们还是得需要手动刷新才能看到改动结果. 这是我们接下来需要解决的.
分离配置
作为开发设置有它自已的某些需求, 我们需要分离我们的Webpack配置文件.鉴于Webpack的配置全部是JavaScript的,所以我们有多种发式达到这个目地,至少以下的方式是可行的:
- 管理配置到多个文件,通过
--config
参数指向Webpack.通过模块引入共享配置. 你可以在webpack/react-starter这篇文章中得到灵感. - 将配置推送到一个库,然后使用.例如: HenrikJoreteg/hjs-webpack.
- 管理配置在单个文件使用分支.如我们触发脚本(例如:
npm run test
), npm 在环境变量中设置这些信息,我们可以匹配并返回我们想要的配置.
我更喜欢最后的方式,因为它让我可以知道发生了什么,非常适合小型项目.
To Keep things simple, 我已经定义了一个merge
函数,连接数组和合并对象,我们马上就会知道,这在Webpack中是很方便的, 执行
npm i webpack-merge --save-dev
将它加入到工程中.
接下来,我们需要定义一些分离点到我们的配置,自定义它在每个npm脚本.例如:
webpack.config.js
...
插入开始
const merge = require('webpack-merge');
插入结束
插入开始
const TARGET = process.env.npm_lifecycle_event;
插入结束
const PATHS = {
app: path.join(__dirname, 'app'),
build: path.join(__dirname, 'build')
};
删除开始
module.exports = {
删除结束
插入开始
const common = {
插入结束
// Entry accepts a path or an object of entries. We'll be using the
// latter form given it's convenient with more complex configurations.
entry: {
app: PATHS.app
},
output: {
path: PATHS.build,
filename: 'bundle.js'
}
};
插入开始
// Default configuration
if(TARGET === 'start' || !TARGET) {
module.exports = merge(common, {});
}
if(TARGET === 'build') {
module.exports = merge(common, {});
}
插入结束
现在我们可以进行扩展了,我们能连接到模块热插拔使浏览器刷新,这对开发是有帮助的.
配置模块热插拔(HMR)
HMR可以当我们做出改变时自动刷新浏览器, 我们想法是如果我们改变了 app/component.js,浏览器将自动刷新,同样适用于CSS的改变.
我了使它工作.我们需要连接到开发服务器,运行在内存中打包.Webpack使用基于WebSocket通信方式来达到这个目地. 我们让Webpack通过开发服务器的inline选项,来生成客户端部分,这个选项包含客户端需要的由HMR到Webpack生成的包的脚本.
webpack.config.js
...
leanpub-start-insert
const webpack = require('webpack');
leanpub-end-insert
...
if(TARGET === 'start' || !TARGET) {
leanpub-start-delete
module.exports = merge(common, {});
leanpub-end-delete
leanpub-start-insert
module.exports = merge(common, {
devServer: {
contentBase: PATHS.build,
// Enable history API fallback so HTML5 History API based
// routing works. This is a good default that will come
// in handy in more complicated setups.
historyApiFallback: true,
hot: true,
inline: true,
progress: true,
// Display only errors to reduce the amount of output.
stats: 'errors-only',
// Parse host and port from env so this is easy to customize.
//
// If you use Vagrant or Cloud9, set
// host: process.env.HOST || '0.0.0.0';
//
// 0.0.0.0 is available to all network devices unlike default
// localhost
host: process.env.HOST,
port: process.env.PORT
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
});
leanpub-end-insert
}
...
鉴于我们在配置文件中配置了contentBase
,故我们在package.json中移除它.
package.json
...
"scripts": {
"build": "webpack"
leanpub-start-delete
"start": "webpack-dev-server --content-base build"
leanpub-end-delete
leanpub-start-insert
"start": "webpack-dev-server"
leanpub-end-insert
},
...
执行 npm start
并转到localhost:8080,尝试一下修改app/component.js,会发现浏览器跟着刷新.注意当你修改JavaScript代码,这是硬刷新,CSS变动在更加友好的方式,可以不刷新应用.在下一章我们研究怎样在React中实现类似的事情,这将提供我们更好的开发经验.
如果你想改变默认端口,可以配置
process.env.PORT || 3000
.
HMR on Windows
略.
CSS 刷新
我们可以扩展这个方式对于CSS.Webpack允许我们改变CSS时不会强制完全刷新. 加载CSS到工程中,我们需要使用一对加载器.首先,调用:
npm i css-loader style-loader --save-dev
现在我们加载了需要的.我们还要确保Webpack知道它们.配置:
webpack.config.js
...
const common = {
...
leanpub-start-delete
}
leanpub-end-delete
leanpub-start-insert
},
module: {
loaders: [
{
// Test expects a RegExp! Note the slashes!
test: /\.css$/,
loaders: ['style', 'css'],
// Include accepts either a path or an array of paths.
include: PATHS.app
}
]
}
leanpub-end-insert
}
...
这个配置指的是以.css结尾的文件应该调用给定的加载器.test
是JavaScript匹配样式文件的正则表达式.加载器是从右到左的顺序执行的.即先执行css-loader,然后执行style-loader. css-loader将处理我们CSS文件中的@import
和url
语句.style-loader在我们的JavaScript中的require
语句.对CSS的预处理,像Sass和Less,并且加载它们.
设置初始CSS
我们为程序增加一点儿CSS:
app/main.css
body {
background: cornsilk;
}
此外我们需要让Webpack知道到它.没有require
指向它,Webpack将不能找到这个文件.
app/index.js
leanpub-start-insert
require('./main.css');
leanpub-end-insert
...
执行npm start
,并打开你的浏览器到localhost:8080.
试着改变main.css 例如background: lime
使得它看起来漂亮一些.
加入 Sourcemaps
为了提高debug的能力.我们能配置一个sourcemaps.它能让我们看到错误的准确位置.在Webpack是通过devtool
来设置的.
webpack.config.js
...
if(TARGET === 'start' || !TARGET) {
module.exports = merge(common, {
leanpub-start-insert
devtool: 'eval-source-map',
leanpub-end-insert
...
});
}
...
如果你运行npm start
.Webpack将生成sourcemaps.Webpack支持多种不同的生成方式.详细请看官方文档.在上面的例子里,我们使用eval-source-map
.它起初构建很慢.但是它支持快速重建和得到直实的文件.
更快速的开发指定选项,如cheap-module-eval-source-map
和 eval
,生成低质量的sourcemaps.所有eval
选项将把sourcemaps作为你JavaScript代码的一部分,因此,他们并不适合生产环境.但在开发过程中,往往是适合的用例.
有可能你需要在你的浏览器中启用sourcemaps选项.
使用npm-install-webpack-plugin避免npm install
为了避免一些输入,我们可以设置一个Webpack插件npm-install-webpack-plugin.随着我们的发展项目,它将检测到 Webpack 配置和项目文件所做的更改并为我们安装依赖项.它将自动修改package.json.
总结
在这章学习了,我们学习了使用Webpack进行构建和开发.当前的配置是为了更好的开发而不是用于生产环境.下一章我们将应用到React中.