webpack启蒙教学
自我介绍环节
webpack.pngwebpack 是一个现代 JavaScript 应用程序的模块打包器(module bundler) 。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成少量的 bundle(通常只有一个,由浏览器加载)。
如上是webpack官网上的简介。从中我们可以看出webpack是一个模块打包器,它获取带依赖的模块并产生出与这些模块相对于的静态资源。然而,自从发布后,webpack逐渐发展成为所有前端代码的管理工具。
为什么使用Webpack
分块转换的想法
模块要能够在客户端中去执行,则必须将它们通过请求从server端下载到browser端。通常一个请求对应一个模块,只有需要的模块会被转换。当所有资源都在一个模块中时,不需要的模块也会被转换,这样就显得很浪费资源和时间。将众多的模块切成许多片,在初始化时的请求不会包括完整的代码,并且在初始化时不需要的模块切片会在后续加载过程中按需加载。并且将模块化的切片方式是可以由开发人员自己定义的。
常用的模块系统解决方案:
一、<script>
标签类型
最原汁原味的脚本引入方案,缺点如下(请不要问我为什么不介绍优点):
- 全局作用域下造成变量的冲突;
- 文件加载顺序很重要;
- 模块与模块之间的依赖要解决;
- 在大型项目中难以维护和管理;
二、CommonJs
该规范的核心思想是允许模块通过require方法来同步加载所要依赖的其他模块,然后通过exports
或module.exports
来导出需要暴露的接口。
require("module");
require("../file.js");
exports.doStuff = function() {};
module.exports = someValue;
优点:
- 服务端模块能够重复利用;
- 有优秀的包管理工具npm;
- 简单,上手容易;
缺点:
- 同步的模块加载方式不适合在浏览器环境中,同步意味着阻塞加载,浏览器资源是异步加载的;
- 不能非阻塞的并行加载多个模块;
三、AMD
由于浏览器端的模块不能采用同步的方式加载,会影响后续模块的加载执行,因此AMD(Asynchronous Module Definition异步模块定义)规范诞生了。
define("module", ["dep1", "dep2"], function(d1, d2) {
return someExportedValue;
});
require(["module", "../file"], function(module, file) { /* ... */ });
require接口用来加载一系列模块,define接口用来定义并暴露一个模块。
优点:
- 适合浏览器的异步加载机制;
- 并行加载模块;
缺点:
- 提高了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不顺畅;
四、CMD
CMD(Common Module Definition)规范和AMD很相似,尽量保持简单,并与CommonJS和Node.js的 Modules 规范保持了很大的兼容性。在CMD规范中,一个模块就是一个文件。
define(function(require, exports, module) {
var $ = require('jquery');
var Spinning = require('./spinning');
exports.doSomething = ...
module.exports = ...
})
优点:
- 依赖就近,延迟执行;
- 可以很容易在 Node.js 中运行;
缺点:
- 依赖 SPM 打包,模块的加载逻辑偏重;
五、ES6
EcmaScript6标准增加了JavaScript语言层面的模块体系定义。在 ES6 中,我们使用export关键字来导出模块,使用import关键字引用模块。需要说明的是,ES6的这套标准和目前的标准没有直接关系,目前也很少有JS引擎能直接支持。
import "jquery";
export function doStuff() {}
module "localModule" {}
优点:
- 未来的ES规范;
缺点:
- 浏览器对ES6的支持还需要一段时间;
- 能够依赖的现有的模块少;
webpack的目标如下:
- 拆分依赖树成块并按需加载;
- 让初始化加载时间更少;
- 每一个静态资源应该是一个模块;
- 能够集成第三方类库;
- 适用于大型项目;
- 能够定制模块打包的每一个部分;
webpack较之其他类似工具有什么不同?
- 有同步和异步两种不同的加载方式;
- 加载器(Loaders)可以将其他资源整合到JS文件中;
- 支持 CommonJs AMD 规范有丰富的开源插件库,可以根据自己的需求自定义webpack的配置
众所周知作为一个SEO的小TIPS,浏览器加载的资源越少,响应的速度也就越快,所以有时候我们通常会尽可能的将资源合并到一个主文件app.js里面。但有时候也会适得其反:当项目十分庞大的时候,不同的页面不能做到按需加载,而是将所有的资源一并加载,耗费时间长,性能降低。会导致依赖库之间关系的混乱,特别是大型项目时,会变得难以维护和跟踪。
虽然webpack也会将所有资源放在一个文件里面,但是webpack可以很好的解决以上缺点,因为它是一个十分聪明的模块打包系统,当你正确配置后,它会比你想象中的更强大,更优秀。
webpack工作流程.png核心概念
入口(Entry)
webpack 创建应用程序所有依赖关系图的起点。入口告诉 webpack 从哪里开始,并根据依赖关系图确定需要打包的内容。可以将应用程序的入口认为是根上下文(contextual root) 或 app 第一个启动文件。参见如下示例:
// 单入口
module.exports = {
entry: {
main: './src/main.js'
}
}
// 多入口
module.exports = {
entry: {
app: ["./home.js", "./events.js"]
}
}
// 多入口,app(应用主入口),vendors(公共库)
module.exports = {
entry: {
app: './src/main.js',
vendors: './src/vendors.js'
}
}
出口(Output)
将所有的资源(assets)归拢在一起后,还需要告诉 webpack 在哪里打包应用程序。webpack 的 output 属性描述了如何处理归拢在一起的代码(bundled code)。
const path = require('path');
module.exports = {
output: {
// 打包输出的目录,这里是绝对路径,必选设置项
path: path.resolve(__dirname, './dist'),
// 资源基础路径
publicPath: '/dist/',
// 打包输出的文件名,也可以设置为[name].js,动态对应入口的定义
filename: 'my-first-bundle.js'
}
}
加载器(Loader)
webpack 会把所有引用到的资源(.css, .html, .scss, .jpg, etc.) 都作为模块处理。然而 webpack 自身只理解 JavaScript。webpack loader 在文件被添加到依赖图中时,将其转换为模块。在 webpack 的配置中 loader 有两个目标:
- 识别出(identify)应该被对应的 loader 进行转换(transform)的那些文件;
- 转换这些文件(test 属性),从而使其最终添加到 bundle 中(use 属性);
示例如下:
module.exports = {
module: {
rules: [
{
// 这是个正则表达式
test: /\.jsx$/,
// 指定loader
loader: 'babel-loader'
},
{
test: /\.css$/,
// 指定多个loader
use: [
'css-loader',
'style-loader'
]
}
]
}
}
插件(Plugins)
loader仅在每个文件的基础上执行转换,而插件(plugins) 更常用于在打包模块的 生命周期执行操作和自定义功能。webpack 的插件系统极其强大和可定制化。想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 来创建它的一个实例。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
// 应用插件
const path = require('path');
const config = {
main: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-bundle.js'
},
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
},
plugins: [
// 压缩JS
new webpack.optimize.UglifyJsPlugin(),
// 生成HTML
new HtmlWebpackPlugin({template: './src/index.html'}) ,
// 分离入口文件vendor作为单独的模块
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.js'
})
]
};
module.exports = config;
完整配置文件DEMO
**webpack.config.js**
var path = require('path'),
webpack = require('webpack'),
ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
devtool: 'eval',
entry: {
main: './src/main.js'
},
resolve: {
// 自动解析确定的扩展
extensions: ['.js', '.jsx'],
// 告诉 webpack 解析模块时应该搜索的目录
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
],
alias: {
'src': path.resolve(__dirname, './src')
}
},
output: {
// 打包输出的目录,这里是绝对路径,必选设置项
path: path.resolve(__dirname, './dist'),
// 资源基础路径
publicPath: '/dist/',
// 打包输出的文件名
filename: 'build.js'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
cacheDirectory: true
}
},
{
test: /\.css$/,
/*
use: [
'css-loader',
'style-loader'
]
*/
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader?minimize'
})
},
// 支持less
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{ loader: "css-loader?minimize" },
{ loader: "less-loader" }
]
})
},
{
// 处理图片文件
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 7186, // inline base64 if <= 7K
name: 'static/images/[name].[ext]'
}
},
{
// 处理字体文件
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 7186, // inline base64 if <= 7K
name: 'static/fonts/[name].[ext]'
}
}
]
},
plugins: [
// https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
new ExtractTextPlugin({ filename: 'static/css/app.css', allChunks: true })
]
}