前言:上一篇文章主要介绍安装和简单使用,但是webpack还有更多高级的功能
生成Source Maps(使调试更容易)
开发总是离不开调试,如果可以更加方便的调试当然就能提高开发效率,不过打包后的文件有时候你是不容易找到出错了的地方对应的源代码的位置的,Source Maps就是来帮我们解决这个问题的。
通过简单的配置后,Webpack在打包时可以为我们生成的source maps,这为我们提供了一种对应编译文件和源文件的方法,使得编译后的代码可读性更高,也更容易调试。
在webpack的配置文件中配置source maps,需要配置devtool,它有以下四种不同的配置选项,各具优缺点,描述如下:
devtool选项 | 配置结果 |
---|---|
source-map | 在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的source map,但是它会减慢打包文件的构建速度; |
cheap-module-source-map | 在一个单独的文件中生成一个不带列映射的map,不带列映射提高项目构建速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便; |
eval-source-map | 使用eval打包源文件模块,在同一个文件中生成干净的完整的source map。这个选项可以在不影响构建速度的前提下生成完整的sourcemap,但是对打包后输出的JS文件的执行具有性能和安全的隐患。不过在开发阶段这是一个非常好的选项,但是在生产阶段一定不要用这个选项; |
cheap-module-eval-source-map | 这是在打包文件时最快的生成source map的方法,生成的Source Map 会和打包后的JavaScript文件同行显示,没有列映射,和eval-source-map选项具有相似的缺点; |
正如上表所述,上述选项由上到下打包速度越来越快,不过同时也具有越来越多的负面作用,较快的构建速度的后果就是对打包后的文件的的执行有一定影响。
在学习阶段以及在小到中性的项目上,eval-source-map是一个很好的选项,不过记得只在开发阶段使用它,继续上面的例子,进行如下配置
配置webpack.config.js文件
module.exports = {
devtool: 'eval-source-map',//配置生成Source Maps,选择合适的选项
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/public",
filename: "bundle.js"
}
}
cheap-module-eval-source-map方法构建速度更快,但是不利于调试,推荐在大型项目考虑da时间成本是使用。
使用webpack构建本地服务器
想不想让你的浏览器监测你的代码的修改,并自动刷新修改后的结果,其实Webpack提供一个可选的本地开发服务器,这个本地服务器基于node.js构建,可以实现你想要的这些功能,不过它是一个单独的组件,在webpack中进行配置之前需要单独安装它作为项目依赖
npm install --save-dev webpack-dev-server
devserver作为webpack配置选项中的一项,具有以下配置选项
devserver配置选项 | 功能描述 |
---|---|
contentBase | 默认webpack-dev-server会为根文件夹提供本地服务器,如果想为另外一个目录下的文件提供本地服务器,应该在这里设置其所在目录(本例设置到“public”目录) |
port | 设置默认监听端口,如果省略,默认为”8080“ |
inline | 设置为true,当源文件改变时会自动刷新页面 |
colors | 设置为true,使终端输出的文件为彩色的 |
historyApiFallback | 在开发单页应用时非常有用,它依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html |
继续把这些命令加到webpack的配置文件中,现在的配置文件如下所示
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//实时刷新
}
}
Loaders
鼎鼎大名的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 + "/public",
filename: "bundle.js"
},
module: {//在配置文件里添加JSON loader
loaders: [
{
test: /\.json-loader$/,
loader: "json"
}
]
},
devServer: {
contentBase: "./public",
colors: true,
historyApiFallback: true,
inline: true
}
}
注意在json中的配置文件中,
test 后期为 json-loader 结尾的-loader 不能省略
创建带有问候信息的JSON文件(命名为config.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;
};
Loaders很好,不过有的Loaders使用起来比较复杂,比如说Babel。
Babel
Babel其实是一个编译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-react
在webpack中配置Babel的方法如下
module.exports = {
// 生成Source Maps(使调试更容易)
devtool: 'eval-source-map',//配置生成Source Maps,选择合适的选项
// 基础配置 不用输入那么长的命令行
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js"//打包后输出文件的文件名
},
module: {
loaders: [
{test: /\.json$/, loader: "json-loader"}, //json
{test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader',//在webpack的module部分的loaders里进行配置即可
query: {presets: ['es2015','react']}}
]
},
// 构建本地服务器,时时刷新
devServer: {
contentBase: "./public",//本地服务器所加载的页面所在的目录
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的模块定义和渲染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 + "/public",
filename: "bundle.js"
},
module: {
loaders: [
{
test: /\.json$/,
loader: "json"
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel'
}
]
},
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 = {
// 生成Source Maps(使调试更容易)
devtool: 'eval-source-map',//配置生成Source Maps,选择合适的选项
// 基础配置 不用输入那么长的命令行
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js"//打包后输出文件的文件名
},
module: {
loaders: [
{test: /\.css$/, loader: "style-loader!css-loader"}, //style
{test: /\.json$/, loader: "json-loader"}, //json
{test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader',//在webpack的module部分的loaders里进行配置即可
query: {presets: ['es2015','react']}}
]
},
node: {
fs: 'empty'
},
// 构建本地服务器,时时刷新
devServer: {
contentBase: "./public",//本地服务器所加载的页面所在的目录
colors: true,//终端中输出结果为彩色
historyApiFallback: true,//不跳转
inline: true//实时刷新
}
}
main.js代码
import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';
import './style.css';//使用require导入css文件
render(<Greeter />, document.getElementById('root'));
也许在webpack 1 中没有遇到,但是我运行之后总是报错
解决方案:在配置文件中增加如下代码
node: {
fs: 'empty'
},
注:感叹号的作用在于使同一文件能够使用不同类型的loader
接下来,在app文件夹里创建一个名字为"style.css"的文件,对一些元素设置样式(简单观察一下)
html,body{
padding: 0;
margin: 0;
background-color: #dfd;
}
通常情况下,css会和js打包到同一个文件中,并不会打包为一个单独的css文件,不过通过合适的配置webpack也可以把css打包为单独的文件的。
不过这也只是webpack把css当做模块而已,咱们继续看看一个真的CSS模块的实践。
CSS module
在过去的一些年里,javascript通过一些新的语言特性,更好的工具以及更好的实践方法(比如说模块化)发展的非常迅速。模块使得开发者把复杂的代码转化为小的,干净的,依赖声明明确的单元,且基于优化工具,依赖管理和加载管理可以自动完成。不过前端的另一部分,CSS发展就相对慢一些,大多的样式表依旧是巨大且充满了全局的类名,这使得维护和修改都非常困难和复杂。
最近有一个叫做 CSS modules的技术就意在把JS的模块化思想带入CSS中来,通过CSS模块,所有的类名,动画名默认都只作用于当前模块。webpack从一开始就对CSS模块化提供了支持,在CSS loader中直接配置后,你所需要做的一切就是把“modules“传递到所需的地方,然后就可以直接把CSS的类名传递到组件的代码中,且这样做的只对当前的组件有效,不必担心在不同的模块中具有相同的类名可能会造成的问题。具体的代码如下
module.exports = {
// 生成Source Maps(使调试更容易)
devtool: 'eval-source-map',//配置生成Source Maps,选择合适的选项
// 基础配置 不用输入那么长的命令行
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js"//打包后输出文件的文件名
},
module: {
loaders: [
{test: /\.css$/, loader: "style-loader!css-loader?modules"}, //style
{test: /\.json$/, loader: "json-loader"}, //json
{test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader',//在webpack的module部分的loaders里进行配置即可
query: {presets: ['es2015','react']}}
]
},
node: {
fs: 'empty'
},
// 构建本地服务器,时时刷新
devServer: {
contentBase: "./public",//本地服务器所加载的页面所在的目录
colors: true,//终端中输出结果为彩色
historyApiFallback: true,//不跳转
inline: true//实时刷新
}
}
就是在原来的 loaders css后面增加了 ?modules
创建一个Greeter.css文件
.root {
background-color: #eee;
padding: 10px;
border: 3px solid #ccc;
}
导入.root到Greeter.js中
//Greeter,js
import React, {Component} from 'react';
import config from './config.json';
import style from './Greeter.css';
class Greeter extends Component{
render() {
return (
<div>
{config.greetText}
</div>
);
}
}
export default Greeter;
放心使用把,相同的类名也不会造成不同组件之间的污染。
CSS modules 也是一个很大的主题,有兴趣的话可以去官方文档查看更多消息