简而言之,webpack是一个JavaScript模块打包工具。但是它可以用来托管并且管理你所有的前端代码(至少它被开发出来的目的或者说社区希望达到的目标是这样的)。
What is webpack
传统的构建工具执行任务的方式是: 你的css以及javascipt等是分离的。你必须分开的去管理他们,并且你要确保每一项在生产环境中都是合适的状态。
比如gulp可以担当预处理器和转换器的角色,但是每一个场景下,它都会有源输入(目录或者文件)和一个编译打包后的目标输出(目录或者文件)。但是它仅仅是按照这个规则一个接着一个的执行却没有在宏观上去考虑如何和整个应用更好的结合。这会给开发者带来一些负担:如何选择以及怎样筛选合适的任务处理器;在生产环境中去将这些分离的部件很好的啮合到一起。
Webpck抛出一些问题: 如果在开发的过程中某一个工具会自行帮我们处理应用中模块之间的依赖, 我们只需要专注在我们自己的业务代码层面以及最终产出,构建只需要经过简单的配置 那么情况会变得怎么样呢?
webpack的处理方式: 如果webpack知道你想要什么,他只会打包生产环境中实际用得到的模块。
如果你在最近几年活跃在各大web社区,我想你已经知道了解决问题的办法了: 使用javascript来实现。 Webpack尝试去使用javascript来处理模块之间的依赖使构建过程更加简单。 但是这个设想强大的地方不是仅仅的模块管理,而是整个构建层面100%是由javascript来实现,伴随node的特性,它可以做的更多。Webpack让你能够写出更契合系统的有效的javascript代码。
换句话说:你不必为webpack编写代码,单纯的for your project。
但是webpack能够和你的项目完美共生(当然需要增加一些额外的配置,如果你的项目有新的构建需求)
简而言之, 如果你曾经被下面的问题困扰过:
1、模块加载的顺序问题。
2、在生产环境中引用了没有被使用的css和js。
3、意外的加载了一些库多次。
4、作用域引发的问题(css 和 js中都会遇到)。
5、寻找一个好的工具以便更好使用node / bower javascript 模块,如果它能通过配置去帮你优化这些模块就更棒了。
6、优化项目的时候担心对项目造成未知的破坏
那么webpack应该会帮助到你,上面提到的问题,它都能通过javascript来解决。最棒的是什么?
Webpack可以纯粹的在服务端运行,这意味着你可以使用webpack来开发渐进增强(progressively-enhanced)的websites。
我们使用的是Yarn(brew install yarn),没有用npm,这不影响我们的后面的demo,他们达到的目的是一样的。我们在执行下面的命令,将webpack添加到全局module和本地项目中。
yarn global add webpack@beta webpack-dev-server@beta
yarn add --dev webpack@beta webpack-dev-server@beta
我们新建一个webpack配置文件,在我们项目的根目录下面
const path = require('path');
const webpack = require('webpack');
module.exports = {
context: path.resolve(__dirname, './src'), entry: { app: './app.js',
},
output: {
path: path.resolve(__dirname, './dist'), filename: '[name].bundle.js',
},
};
ps: __dirname 指向当前项目的根目录
Webpack通过阅读你的代码 可以‘知道’项目中运行的内容(‘别担心,它签了保密协议的’),
webpack做了下面几件事:
1、从context目录启动。
2、它通过entry去寻找入口文件(这里对应着是app.js这个文件)。
3、读取内容,只要遇到import 和require 引入的依赖,它就会帮我们先转换,这一步是为绑定到最终的目标文件里面做准备。然后它会去寻找该模块的依赖,该模块的依赖的依赖包,依次往上知道达到没有依赖的位置,然后将所有使用到的依赖包绑定到目标文件。
4、到这一步,webpack将所有的文件都已经打包到了output.path目录下面,并且是以output.filename命名的([name]将会被entry的key替换)。
src/app.js如下,你需要提前安装moment到项目中(yarn add moment)。
import moment from 'moment';
var rightNow = moment().format('MMMM Do YYYY, h:mm:ss a');
console.log(rightNow);
// "October 23rd 2016, 9:30:24 pm"
然后运行:
webpack -p
ps: The p flag is “production” mode and uglifies/minifies output. 这个-p参数 全名字是 production,它会帮我们压缩输出的文件。
你会看到在dist/app.bundle.js 里面记录了日期时间并且有console语句。注意到webpack自动将moment引入了,因为app.js文件里面require了这个包。(尽管如果你当前目录有一个moment.js的文件,然而webpack会优先引用Node Module里面的moment module)。
你可以修改entry对象来指定任意数量的入口/输出文件,entry的值可以是字符串,数组,对象。
多个文件,一起打包。
const path = require('path');
const webpack = require('webpack');
module.exports = {
context: path.resolve(__dirname, './src'),
entry: {
app: ['./home.js', './events.js', './vendor.js'], },
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].bundle.js',
},
};
最终会将这3个文件一起打包进disa/app.bundle.js文件,按照数组的索引来排序
多个入口文件,多个输出文件
const path = require('path');
const webpack = require('webpack');
module.exports = {
context: path.resolve(__dirname, './src'), entry: {
home: './home.js', events: './events.js', contact: './contact.js', },
output: {
path: path.resolve(__dirname, './dist'), filename: '[name].bundle.js',
},
};
当然你也可以选择将多个js文件打包进你的app,在dist目录下会有三个文件
home.bundle.js,events.bundle.js,contact.bundle.js
Advanced auto-bundling
如果你将你的应用分别打包,输出多个文件(如果你的app内部存在巨量的js,而这个js你不需要提前加载这就有必要了),有可能在打包之后的多个文件可能有重复的代码,因为webpack将多个文件内需要的依赖依次打包进了多个目标文件里面。幸运的是 webpack提供了CommonsChunk插件来解决这个问题:
module.exports = {
// …
plugins: [
new webpack.optimize.CommonsChunkPlugin({ name: 'commons',
filename: 'commons.js',
minChunks: 2,
}),
],
// …
};
现在,在你的输出目标文件内,如果有任何模块被加载了2次(这个是由参数minChunks来决定的),那么这个模块将会被打包进一个名字为common.js(看上面代码由参数filename来决定)这个文件可以缓存到客户端,虽然多一次额外的网络请求,但是这也阻止了客户端多次下载重复的库。许多情况下都是这样网络换取速度。
Webpack 实际上有它自己的开发服务器,因此无论你正在开发一个静态页面或者做前端原型,它都非常适合。你可以增加一个devServer 对象 在 webpack.config.js。
module.exports = {
context: path.resolve(__dirname, './src'), entry: {
app: './app.js',
}, output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, './dist/assets'),
publicPath: '/assets', // New },
devServer: {
contentBase: path.resolve(__dirname, './src'), // New
},
};
在src/index.html增加下面代码:
<script src="/assets/app.bundle.js"></script>
终端里面运行:
webpack-dev-server
你的服务器已经运行在8080端口上。请注意在scipt标签里面的 /assets ,它匹配的是output对象里面的publicPath(类似于express里面的静态目录),你可以任意命名,这一点在cdn加速的时候特别有用。
Globally-accessible methods
要用全局命名空间里面的特定函数?在webpack.config.js里面配置output.library。
module.exports = {
output: {
library: 'myClassName',
}
};
这会在window对象上绑定一个myClassName的实例,现在在入口文件里面可以调用这个实例里面的方法,或者值。
到目前为止,我们用到的语言仅仅是javascript, 这是必要的因为Webpack使用的唯一语言就是它 webpack自身只能处理javascript模块。但是其他类型的模块和文件(比如sass,css。
webpack自身没有办法处理,但是只要我们将它们经过javascript处理一次转换成我们需要的类型。我们就就它加载器或者转换器。
加载器可以扮演预处理的角色(编译sass),或者转换角色(比如babel,将es6或者React 转换到es5),在npm上,通常他们以 *-loader的方式命名,比如前面的 sass-loader 和 babel-loader。
由于现在es6兼容性问题,我们想使用es6的特性就得需要babel的转换了,首先我们要安装合适的加载(转换)器:
yarn add --dev babel-loader babel-core babel-preset-es20151
然后在webpack.config.js下面配置:
module.exports = {
// …
module: {
rules: [
{ test: /.js$/,
use: [
{ loader: 'babel-loader',
options: { presets: ['es2015']
}
}],
},
// Loaders for other file types can go here ], },
// …
};
对于webpack 1.x的用户来说, 加载器的核心概念和2.x来说是一致的。但是语法有一些变化(比如1.x中是loaders 而不是rules),因为现在仍然处于发布候选版阶段,所以在正式版文档之前这些语法可能都不是最准确和合适的。
test的正则对象 /.js$/ 的目的是去寻找babel-loder去加载的所有以.js结尾的文件。Webpack通过这个正则允许开发者去控制那些文件可以被转换或者编译,当然他不会强制你采用那些文件扩展名,只需要你以合适的方式组织你的文件,这个正则匹配到了就好。
比如:在/my_legacy_code/这个目录下面,你没有使用es6,你可以修改上面test的值/^((?!my_legacy_code).)*.js$/ ,babel-loader 会过滤掉这个特殊的文件夹,转换其他的文件。
CSS + Style Loader
我们也可以通过css-loader引入css文件,比如我们当前项目目录有 index.js文件,我们可以这样导入:
import styles from './assets/stylesheets/application.css';1
当然直接这样一句代码是不行的,会报错:You may need an appropriate loader to handle this file type,记住webpack只能解析javascript,因此我们要安装合适的loader。
yarn add --dev css-loader style-loader
然后在webpack.config.js里面配置:
module.exports = {
// …
module: {
rules: [
{ test: /.css$/,
use: ['style-loader', 'css-loader'],
},
// …
],
},
};
加载器的执行顺序是数组索引的逆序,这意味着先会运行css-loader,然后再是style-loader。
你会注意到在生产环境下,webpack在打包css的同时也会将css同时打包,style-loader实际上做的实质性内容就是将你的css代码写入到html里面的head标签里面。虽然乍看一下觉得有点奇怪,但是慢慢的你会发现,那其实是合理的。
试想,你肯定做过这样的优化,少向服务端发送一个请求–就节约了网络请求的时间-如果你采用js加载dom的方式,本质上也就避免Flash of unstyled content。(参考地址: https://en.wikipedia.org/wiki/ ... ntent )
你也会注意到,webpack会自动的将@import 队列里面css文件打包成一个(并非像css默认的importy引入方式,它会增加更多的请求降低加载静态资源的速度)
在js文件中加载css是非常棒的,因为你能以一种新的方式模块化你的css。你可以在button.js里面引用button.css,这意味着如果button.js没有被使用,那么button.css里面的样式永远不会出现在生产环境中。如果你非常推崇css组件模块化这一套体系比如 SMACSS 或者BEM(参考地址: https://smacss.com/book/categorizing ),你在编写css与你和js的时候发现它的精妙所在。
想使用sass,没问题!
yarn add --dev sass-loader node-sass
增加如下规则:
module.exports = {
// …
module: {
rules: [
{ test: /.(sass|scss)$/,
use: [ 'style-loader',
'css-loader', 'sass-loader',
]
}
// …
],
},
};
这样在js文件中可以通过import引入sass或者scss文件了。
如果你想将css打包进一个单独的文件,而不是依赖bundle将样式以style写入在head标签里面。我们可以使用webpack插件extract-text-webpack-plugin来实现,打开app.js。
import styles from './assets/stylesheets/application.css';
安装这个插件到本地目录。
yarn add --dev extract-text-webpack-plugin@2.0.0-beta.4
在webpack.config.js增加如下配置:
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
// …
module: {
rules: [
{
test: /.css$/, loader: ExtractTextPlugin.extract({ loader: 'css-loader?importLoaders=1',
}),
}, // …
]
},
plugins: [ new ExtractTextPlugin({ filename: '[name].bundle.css',
allChunks: true,
}),
],
};
在终端运行webpack -p 你会发现在output设置的目录里面生成了一个app.bundle.css文件,这时候在html文件里面增加link引入css文件,打开html,和预期的一样。