webpack初步了解
构建工具
构建工具的功能
- 代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等。
- 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等。
- 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。
- 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。
- 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。
- 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
常用的构建工具
- grunt
- gulp
- fis3(百度)
- webpack模块化管理工具,可以对模块进行:
- 压缩
- 预处理
- 按需打包
- 按需加载
- 热加载
webpack介绍
- webpack可以看做是模块打包机,它做的事情是:
- 分析你的项目结构,找到JavaScript模块;
- 其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等);
- 并将其转换和打包为合适的格式供浏览器使用;
- Webpack和Grunt以及Gulp相比有什么特性
- Webpack和另外两个并没有太多的可比性;
- Gulp/Grunt是一种能够优化前端的开发流程的工具,而WebPack是一种模块化的解决方案;
- Webpack的优点使得Webpack在很多场景下可以替代Gulp/Grunt类的工具;
- Grunt和Gulp的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,工具之后可以自动替你完成这些任务。
- Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。
- webpack特征
- 插件化:webpack本身非常灵活,提供了丰富的插件接口。基于这些接口,webpack开发了很多插件作为内置功能
- 速度快:webpack使用异步IO以及多级缓存机制。所以webpack的速度是很快的,尤其是增量更新。
- 丰富的Loaders:loaders用来对文件做预处理。这样webpack就可以打包任何静态文件。
- 高适配性:webpack同时支持AMD/CommonJs/ES6模块方案。webpack会静态解析你的代码,自动帮你管理他们的依赖关系。此外,webpack对第三方库的兼容性很好。
- 代码拆分:webpack可以将你的代码分片,从而实现按需打包。这种机制可以保证页面只加载需要的JS代码,减少首次请求的时间。
- 优化:webpack提供了很多优化机制来减少打包输出的文件大小,不仅如此,它还提供了hash机制,来解决浏览器缓存问题。
- 开发模式友好:webpack为开发模式也提供了很多辅助功能。比如SourceMap、热更新等。
- 使用场景多:webpack不仅适用于web应用场景,也适用于Webworkers、Node.js场景
webpack命令
- webpack 执行一次开发时的编译
- webpack -p 执行一次生成环境的编译(压缩)
- webpack –watch 在开发时持续监控增量编译(很快)
- webpack -d 让他生成SourceMaps
- webpack –progress 显示编译进度
- webpack –colors 显示静态资源的颜色
- webpack –display-chunks 展示编译后的分块
- webpack –display-modules 列出打包模块
- webpack –display-reasons 显示更多引用模块原因
- webapck –display-error-details 显示更多报错信息
webpack常用配置介绍
出入口 Entry&Output
- entry:入口
- 关联的很多其他需要打包的文件
- output:出口
- path:文件打出的路径,这个path是nodejs内置的方法;
entry和output配置
- path:文件打出的路径,这个path是nodejs内置的方法;
entry配置
- entry是指需要打包的文件;
- entry有几种使用方法:
- 字符串
entry:__dirname+'/src/script/main.js'
//对应
output:{
path:__dirname+'/dist/js',
filename:'bundle.js'
}
- 数组
entry:[
__dirname+'/src/script/main.js',
__dirname+'/src/script/a.js'
]
//对应
output:{
path:__dirname+'/dist/js',
filename:'bundle.js'
}
- 对象
entry:{
main:__dirname+'/src/script/main.js',
a:__dirname+'/src/script/a.js'
}
//对应
output:{
path:__dirname+'/dist/js',
filename:'[name]-[hash].js'
}
//or
output:{path:__dirname+'/dist/js',filename:'[name]-[chunkhash].js'}
output的配置
- output是指打包生成的文件
- entry中输入多个chunk时,为确保文件名唯一避免相互覆盖使用占位符命名filename;
- 三种占位符
- [name]是chunk的name;
- [hash]是本次打包的hash值,hash值相同;
- [chunkhash]是每个chunk的hash值,不同文件同次打包不相同,保证文件的唯一性,只有改变文件中的内容时hash才变化,未做改变的文件hash值不变;
模块 Module
module:模块,在 Webpack眼里一切皆模块,默认只识别js文件, 如果是其它类型文件利用对应的loader转换为js模块。
- rules:做很多的规定,比如:加载css、将es6编译成es5;
- “-loader”其实是可以省略不写的,多个loader之间用“!”连接起来
module: { //加载器配置 loaders: [ //.css 文件使用 style-loader 和 css-loader 来处理 { test: /\.css$/, loader: 'style-loader!css-loader' }, //.js 文件使用 jsx-loader 来编译处理 { test: /\.js$/, loader: 'jsx-loader?harmony' }, //.scss 文件使用 style-loader、css-loader 和 sass-loader 来编译处理 { test: /\.scss$/, loader: 'style!css!sass?sourceMap'}, //图片文件使用 url-loader 来处理,小于8kb的直接转为base64 { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'} ] }
- webpack本身只能加载js模块,如果需要加载其他类型的文件(模块),就需要使用对应的loader进行转换/加载;
- 如果我们想要在js文件中通过require引入模块,比如css或image,那么就需要在这里配置加载器,这一点对于React来说相当方便,因为可以在组件中使用模块化CSS。而一般的项目中可以不用到这个加载器。
- module 的作用是添加loaders, 那loaders有什么作用呢?
模块加载(转换)器 Loader
- loader:模块加载器,将非js模块包装成webpack能理解的js模块;
- loader用于转换应用程序的资源文件。他们是运行在nodejs下的函数,使用参数来获取一个资源的来源,并且返回一个新的来源(资源的位置)。
- 文件loader:
- url-loader 像 file loader 一样工作,但如果文件小于限制,可以返回 data URL
- file-loader 将文件发送到输出文件夹,并返回(相对)URL
- JSON的loader
- json-loader 加载 JSON 文件(默认包含)
- ES5-6的loader
- babel-loader 加载 ES2015+ 代码,然后使用 Babel 转译为 ES5
- css的loader
- style-loader 将模块的导出作为样式添加到 DOM 中
- css-loader 解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码
- less-loader 加载和转译 LESS 文件
- sass-loader 加载和转译 SASS/SCSS 文件
- stylus-loader 加载和转译 Stylus 文件
- 代码规范loader
- eslint-loader PreLoader,使用 ESLint 清理代码
vue的loader
- vue-loader 加载和转译 Vue 组件
resolve
- alias可以用于定义别名,用过seajs等模块工具的都知道alias的作用,比如我们在这里定义了ui这个别名,那么在模块中想引用ui目录下的文件,就可以直接这样写:
require('ui/dialog.js');
不用加上前面的更长的文件路径。
- alias可以用于定义别名,用过seajs等模块工具的都知道alias的作用,比如我们在这里定义了ui这个别名,那么在模块中想引用ui目录下的文件,就可以直接这样写:
resolve: {
//查找module的话从这里开始查找
root: 'E:/github/flux-example/src', //绝对路径
//自动扩展文件后缀名,意味着我们require模块可以省略不写后缀名
extensions: ['', '.js', '.json', '.scss'],
//模块别名定义,方便后续直接引用别名,无须多写长长的地址
alias: {
AppStore : 'js/stores/AppStores.js',//后续直接 require('AppStore') 即可
ActionType : 'js/actions/ActionType.js',
AppAction : 'js/actions/AppAction.js'
}
}
loader学习
- 学习网址:https://webpack.js.org/concepts/loaders/;
- Webpack 本身只能处理原生的js模块,但是loader转换器可以将各种类型的资源转换成js模块。这样,任何资源都可以成为Webpack可以处理的模块。
使用loader的三种方式
- 在文件中直接引入loader文件,es6的语法
//es6的语法
import Styles from 'style-loader!css-loader?modules!./styles.css';
//commonjs语法
require('style-loader!css-loader?modules!./styles.css')
- 在命令行界面使用的方式CLI
//直接执行
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
- 在配置文件中使用loader
module: {
rules: [
{
<!-- 首先对资源一个正则匹配 -->
test: /\.css$/,
<!-- 匹配成功后会使用多个loader对其进行处理 -->
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
]
}
babel
- Babel通过语法转换器支持最新版本的JavaScript。这些插件允许你立刻使用新语法,无需等待浏览器支持。
- 在src文件夹下创建component文件夹,component里面创建layer组件,组件里有js、css、html文件;
- 在src文件夹中创建入口js文件,app.js;
//app.js中引入html
import layer from './component/layer/layer.js'
const App = function() {
console.log(layer)
}
new App()
//layer.js中渲染模板
import tpl from './layer.html'
function layer() {
return {
name: 'layer',
tpl: tpl
}
}
export default layer;
- 安装babel转化es6的代码
npm install --save-dev babel-loader babel-core
(webpack3版本) - 使用babel加载器将es6的语法进行转化,转化时要指定参数,方式有三种:
- 使用配置指定
//在配置webpack.config.js中添加
module: {
rules: [{
test: /\.js$/,
loader: "babel-loader",
//加快打包速度的配置
//排除范围
exclude: __dirname + '/node_modules/',
//babel-loader的处理范围
include: '/src/',
//webpack3中使用options代替query
options: {
'presets': ['env']
}
}]
}
- 在package.json中添加
"babel":{
"presets":["env"];
}
- 根目录下创建.babelrc文件
{
"presets": ["env"]
}
插件 Plugin
- plugin:插件,在webpack构建流程中的特定时机插入具有特定功能的代码;
//使用了CommonsChunkPlugin用于生成公用代码,不只可以生成一个,还能根据不同页面的文件关系,自由生成多个,例如:
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
entry: {
p1: "./page1",
p2: "./page2",
p3: "./page3",
ap1: "./admin/page1",
ap2: "./admin/page2"
},
output: {
filename: "[name].js"
},
plugins: [
new CommonsChunkPlugin("admin-commons.js", ["ap1", "ap2"]),
new CommonsChunkPlugin("commons.js", ["p1", "p2", "admin-commons.js"])
]
};
// 在不同页面用<script>标签引入如下js:
// page1.html: commons.js, p1.js
// page2.html: commons.js, p2.js
// page3.html: p3.js
// admin-page1.html: commons.js, admin-commons.js, ap1.js
// admin-page2.html: commons.js, admin-commons.js, ap2.js
- 例:
module.exports = {
devtool: "source-map", //生成sourcemap,便于开发调试
entry: getEntry(), //获取项目入口js文件
output: {
path: path.join(__dirname, "dist/js/"), //文件输出目录
publicPath: "dist/js/", //用于配置文件发布路径,如CDN或本地服务器
filename: "[name].js", //根据入口文件输出的对应多个文件名
},
module: {
//各种加载器,即让各种文件格式可用require引用
loaders: [
// { test: /\.css$/, loader: "style-loader!css-loader"},
// { test: /\.less$/, loader: "style-loader!csss-loader!less-loader"}
]
},
resolve: {
//配置别名,在项目中可缩减引用路径
alias: {
jquery: srcDir + "/js/lib/jquery.min.js",
core: srcDir + "/js/core",
ui: srcDir + "/js/ui"
}
},
plugins: [
//提供全局的变量,在模块中使用无需用require引入
new webpack.ProvidePlugin({
jQuery: "jquery",
$: "jquery",
// nie: "nie"
}),
//将公共代码抽离出来合并为一个文件
new CommonsChunkPlugin('common.js'),
//js文件的压缩
new uglifyJsPlugin({
compress: {
warnings: false
}
})
]
};
webpack-dev-server
- webpack-dev-server是一个轻量级的服务器,修改文件源码后,自动刷新页面将修改同步到页面上;
- webpack-dev-server的他爹和他爹的朋友
- webpack-dev-middleware:作为一个 webpack 中间件,它会开启 watch mode 监听文件变更,并自动地在内存中快速地重新打包、提供新的 bundle,自动编译(watch mode)+速度快(全部走内存)。
- webpack-hot-middleware:
- webpack 可以通过配置 webpack.HotModuleReplacementPlugin 插件来开启全局的 HMR 能力;
- 开启后 bundle 文件会变大一些,因为它加入了一个小型的 HMR 运行时(runtime),当你的应用在运行的时候,webpack 监听到文件变更并重新打包模块时,HMR 会判断这些模块是否接受 update,若允许,则发信号通知应用进行热替换。
- webpack-dev-server是一个小型的Node.js Express服务器,它使用webpack-dev-middleware来服务于webpack的包,除此自外,它还有一个通过Sock.js来连接到服务器的微型运行时
- webpack-dev-server是一个独立的NPM包,你可以通过npm install webpack-dev-server来安装它。
- webpack-dev-server配置
var WebpackDevServer = require("webpack-dev-server");
var webpack = require("webpack");
var compiler = webpack({});
var server = new WebpackDevServer(compiler, {
contentBase: "/path/to/directory",
hot: true,
historyApiFallback: false,
compress: true,
proxy: {
"**": "http://localhost:9090"
},
setup: function(app) {},
staticOptions: {},
quiet: false,
noInfo: false,
lazy: true,
filename: "bundle.js",
watchOptions: {
aggregateTimeout: 300,
poll: 1000
},
publicPath: "/assets/",
headers: { "X-Custom-Header": "yes" },
stats: { colors: true }
});
server.listen(8080, "localhost", function() {});
devServer配置项
- contentBase
- 即 SERVERROOT,如 “path.join(__dirname, “src/html”)”,后续访问 http://localhost:3333/index.html 时,SERVER 会从 src/html 下去查找 index.html 文件。
- 它可以是单个或多个地址的形式:(若不填写该项,默认为项目根目录。)
//单个
contentBase: path.join(__dirname, "public")
//多个:
contentBase: [path.join(__dirname, "public"), path.join(__dirname, "assets")]
- port
- 即监听端口,默认为8080。
- compress
- 传入一个 boolean 值,通知 SERVER 是否启用 gzip。
- hot
- 传入一个 boolean 值,通知 SERVER 是否启用 HMR。
- https
- 可以传入 true 来支持 https 访问,也支持传入自定义的证书:
https: true
//也可以传入一个对象,来支持自定义证书
https: {
key: fs.readFileSync("/path/to/server.key"),
cert: fs.readFileSync("/path/to/server.crt"),
ca: fs.readFileSync("/path/to/ca.pem"),
}
- proxy
- 代理配置,适用场景是,除了 webpack-dev-server 的 SERVER(SERVER A) 之外,还有另一个在运行的 SERVER(SERVER B),而我们希望能通过 SERVER A 的相对路径来访问到 SERVER B 上的东西。
devServer: {
contentBase: path.join(__dirname, "src/html"),
port: 3333,
hot: true,
proxy: {
"/api": "http://localhost:5050"
}
}
//运行 webpack-dev-server 后,若访问 http://localhost:3333/api/user,则相当于访问 http://localhost:5050/api/user。
publicPath
- 如同 webpack-dev-middleware 的 publicPath 一样,表示从内存中的哪个路径去存放和检索静态文件;
- 不过官方文档有一处错误需要堪正 —— 当没有配置 devServer.publicPath 时,默认的 devServer.publicPath 并非根目录,而是 output.publicPath;
- 这也是为何咱们的例子里压根没写 devServer.publicPath,但还能正常请求到 https://localhost:3333/assets/bundle.js。
setup
- webpack-dev-server 的服务应用层使用了 express,故可以通过 express app 的能力来模拟数据回包,devServer.setup 方法就是干这事的:
devServer: {
contentBase: path.join(__dirname, "src/html"),
port: 3333,
hot: true,
setup(app){ //模拟数据
app.get('/getJSON', function(req, res) {
res.json({ name: 'vajoy' });
});
}
}
webpack插件
webpack 通过 plugins 实现各种功能。常见的 plugins 如下:
- webpack.DefinePlugin 定义环境变量;
const webpack = require('webpack'); const NODE_ENV = process.env.NODE_ENV; // 从命令行环境获取 NODE_ENV 参数 module.exports = { plugins: [ new webpack.DefinePlugin({ 'process.env': { 'NODE_ENV': JSON.stringify(NODE_ENV) } // 定义浏览器中的替换的变量为 `process.env.NODE_ENV` }) ] }
- webpack.EnvironmentPlugin 定义环境变量;
const webpack = require('webpack'); module.exports = { plugins: [ new webpack.EnvironmentPlugin([ 'NODE_ENV' ]) ] }
- webpack.optimize.CommonsChunkPlugin 共用 js 打包
- html-webpack-plugin 使用模版生成 html 文件
- webpack-visualizer-plugin 输出依赖文件分析图表
- webpack.HotModuleReplacementPlugin 代码热更新,用于调试模式
- webpack.optimize.OccurrenceOrderPlugin 调整模块的打包顺序,用到次数更多的会出现在文件的前面
- webpack.NoErrorsPlugin 构建过程中有报错,不认为构建完成
- webpack.ProgressPlugin 输出构建进度
- webpack.BannerPlugin 在文件头添加注释
- webpack.optimize.UglifyJsPlugin 压缩 js
- webpack.optimize.DedupePlugin 去除重复依赖
- extract-text-webpack-plugin 从 js 中提取出样式文件,单独打包成 css 文件;
- clean-webpack-plugin每次运行清除指定目录下打包文件;
extract-text-webpack-plugin
- 作用:该插件的主要是为了抽离css样式,防止将样式打包在js中引起页面样式加载错乱的现象;
- 安装:
npm install extract-text-webpack-plugin --save-dev
- 插件参数:
- use:指需要什么样的loader去编译文件,这里由于源文件是.css所以选择css-loader
- fallback:编译后用什么loader来提取css文件
- publicfile:用来覆盖项目路径,生成该css文件的文件路径
- 使用:
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader", // 编译后用什么loader来提取css文件
use: "css-loader" // 指需要什么样的loader去编译文件,这里由于源文件是.css所以选择css-loader
})
}
]
},
plugins: [
new ExtractTextPlugin("styles.css"),
]
}
开发/生产环境打包
- webpack.base.conf.js
- 开启source Map ,帮助调试
webpack项目中的最佳配置
- webpack官方提供的配置方法是通过module.exports返回一个json,但是这种场景不灵活,不能适配多种场景。
- 比如要解决:production模式和development模式,webpack的配置是有差异的,大致有两种思路。
- 1、两份配置文件
webpack.config.production.js/webpack.config.development.js
,然后不同场景下,使用不同的配置文件。 - 2、通过module.exports返回函数,该函数能接受参数。
- 相对来说,第一种更简单,但是重复配置多;第二种更灵活,推荐第二种方式。
- 1、两份配置文件
//返回函数的方式的配置代码架子如下:
module.exports = function(env) {
return {
//上下文
context: config.context,
//入口文件,是所有依赖关系的入口,webpack从这个入口开始静态解析,分析模块之间的依赖关系。
entry: config.src,
//打包输出的配置
output: {
path: path.join(config.jsDest, project),
filename: '[name].js',
chunkFilename: '[name].[chunkhash:8].js',
publicPath: '/assets/' + project + '/'
},
//SourceMap选项,便于开发模式下调试。
devtool: "eval",
//监听模式,增量更新,开发必备!
watch: false,
//优化。
profile: true,
//webpack构建的过程中会生成很多临时的文件,打开cache可以让这些临时的文件缓存起来,从而更快的构建。
cache: true,
//loaders用来对文件做预处理。这样webpack就可以打包任何静态文件。
module: {
loaders: getLoaders(env)
},
//模块别名,这样可以更方便的引用模块。
resolve: {
alias: getAlias(env)
},
//webpack的一些内置功能均是以插件的形式提供。
plugins: getPlugins(env)
};
}
gitHub参考地址:https://github.com/mutouafangzi/webpackDemo20180222