webpack支持AMD和CommonJS,以及其他的一些模块系统,并且兼容多种JS书写规范(如:.coffee,.less),可以处理模块间的依赖关系,所以具有更强大的JS模块化的功能,它能对静态资源进行统一的管理以及打包发布。在 webpack 里,所有类型的文件都可以是模块,包括我们最常见的 JavaScript,及 CSS 文件、图片、json 文件等等。通过 webpack 的各种加载器,我们可以更高效地管理这些文件。
Webpack 会分析入口文件,解析包含依赖关系的各个文件。这些文件(模块)都打包到 bundle.js 。Webpack 会给每个模块分配一个唯一的 id 并通过这个 id 索引和访问模块。在页面启动时,会先执行 entry.js 中的代码,其它模块会在运行 require
的时候再执行。
最简单的webpack.config.js:
module.exports = { entry: './main.js', // 入口文件,常写成path.resolve(__dirname,'src/app.js') , path需要提前require('path') output: { path: __dirname, filename: 'bundle.js' // 输出文件的目录和名称 } }
执行./node_modules/.bin/webpack命令(webpack --progress --colors通过参数让编译的输出内容带有进度和颜色,--watch参数可以开启监听模式,实现修改后自动增量编译(非自动刷新),--display-error-details打印错误详情,-p会针对发布环境编译也就是会压缩代码,package.json中的脚本部分已经默认在命令前添加了node_modules/.bin
路径,所以无论是全局还是局部安装的Webpack,不需要写前面那指明详细的路径。),就会生成bundle.js(在index.html中事先引入<script src='./bundle.js'></script>)。目前为止,其实webpack啥也没做,bundle.js和main.js也没啥区别,也不能再main.js中写import '.../文件',因为浏览器目前还不支持import。借助webpack的loader:babel-loader就可以实现es6的加载方式了。
babel-loader:
安装 babel-loader:
npm install babel-loader babel-core babel-preset-es2015 --save-dev
配置 webpack.config.js的module字段:
module: { loaders: [{ test: /.js$/, // babel内置对jsx的支持,所以也可写成test: /.js|jsx$/, loaders: ['babel-loader?presets[]=es2015'], exclude: /node_modules/ }] }
webpack还有一个配置项:resolve: {extensions: ['', '.js', '.jsx'} 用来指定import时可以被省略的文件后缀。
webpack的配置项output下的publicPath就是对应着最终在index.html文件中引用打包好的文件的目录。
在开发阶段为了便于调试,可以打包后的文件配置成source maps,对应的配置项为:devtool: DEBUG ? 'cheap-module-eval-source-map' : false,
webpack的配置文件可以根据环境而不同,如webpack.dev.config.js和webpack.prod.config.js,运行命令:webpack --config webpack.dev.config.js
为了能实时监控文件变化并反应到浏览器上,Webpack还提供了一个基于Node.js Express框架的开发服务器,它是一个静态资源Web服务器,在开发过程中,开发服务器会监听每一个文件的变化,进行实时打包,并且可以推送通知前端页面代码发生了变化,从而可以实现页面的自动刷新。安装webpack-dev-server,执行命令webpack-dev-server --config webpack.dev.config.js。
webpack也可以把多个文件单独打包,以便在多个页面中单独使用。(Webpack 中涉及路径配置最好使用绝对路径,建议通过 path.resolve(__dirname, "app/folder")
或 path.join(__dirname, "app", "folder")
的方式来配置,以兼容 Windows 环境。)
module.exports = { context: __dirname + "/src", // context表示webpack从context指定的文件夹开始寻找entry里的文件 entry: { // app: ["./home.js", "./events.js", "./vendor.js"], 这表示多个文件打包成一个文件:app.bundle.js home: "./home.js", events: "./events.js", contact: "./contact.js", }, output: { path: __dirname + "/dist", filename: "[name].bundle.js", }, };
加载器:
加载器是webpack准备的一些预处理工具,如用来处理jsx,es6,sass文件等。Webpack 本身只能处理原生的 JavaScript 模块,但是 loader 转换器可以将各种类型的资源转换成 JavaScript 模块。它本身是一个函数,接受源文件作为参数,返回转换的结果。这样,任何资源都可以成为 Webpack 可以处理的模块。按照惯例,而非必须,loader 一般以 xxx-loader
的方式命名,xxx
代表了这个 loader 要做的转换功能,比如 json-loader
。在引用 loader 的时候可以使用全名 json-loader
,或者使用短名 json
。这个命名规则和搜索优先级顺序在 webpack 的 resolveLoader.moduleTemplates
api 中定义。
Default: ["*-webpack-loader", "*-web-loader", "*-loader", "*"]
如编译jsx和es6到原生js,需要按照依赖:babel-loader, babel-core, babel-preset-es2015, babel-preset-react, webpack中的loader配置如下:
module: { loaders: [{ test: /.jsx?$/, include: [path.resolve(__dirname, '../src')], //exclude: [path.resolve(__dirname, '../node_modules')] loader: 'babel', // babel是babel-loader的缩写,-loader都可以不写的 query: { // 为loader提供额外的设置选项(可选) presets: ['es2015', 'react'] } }] }
Babel其实可以完全在webpack.config.js中进行配置,但是考虑到babel具有非常多的配置选项,在单一的webpack.config.js文件中进行配置往往使得这个文件显得太复杂,因此一些开发者支持把babel的配置选项放在一个单独的名为 ".babelrc" 的配置文件中。因此提取出相关部分,分两个配置文件进行配置(webpack会自动调用.babelrc
里的babel配置选项),配置.babelrc中{"presets": ["es2015","react"]}
加载css:
Webpack允许像加载任何代码一样加载 CSS。加载 CSS 需要 css-loader 和 style-loader,他们做两件不同的事情,css-loader会遍历 CSS 文件,然后找到 url() 和@import表达式然后处理他们,style-loader 会把原来的 CSS 代码插入页面中的一个 style 标签中。安装css-loader和style-loader后,配置文件中添加loader:
{ test: /.css$/, loader: 'style!css' //有多个加载器,中间用感叹号隔开,多个加载器从右往左执行 }
加载sass:
安装sass-loader, node-sass,配置{test: /.(sass|scss)$/, loader: 'style!css!sass'}。
转换小图片为base64字符串:
安装url-loader,配置图片如果不大于 25KB 的话要自动在它从属的 css 文件中转成 BASE64 字符串: {test: /.(png|jpeg|gif|jpg)$/,loader: 'url?limit=25000'}
处理大图片:
安装file-loader,配置{test: /.scss$/, loader: 'file?name=images/[name].[ext]'},把图片放到指定目录中。
其他loader:
如使用postcss-loader结合autoprefixer可以自动给css属性加上浏览器前缀。
插件:
插件是用来拓展Webpack功能的,它们会在整个构建过程中生效,执行相关的任务,可以完成更多 loader 不能完成的功能,使用时在 webpack 的配置信息 plugins
选项中指定。Webpack 本身内置了一些常用的插件,也可以通过 npm 安装第三方插件。Loaders和Plugins常常被弄混,但是他们其实是完全不同的东西,可以这么来说,loaders是在打包构建过程中用来处理源文件的(JSX,Scss,Less..),一次处理一个,插件并不直接操作单个文件,它直接对整个构建过程其作用。
压缩插件:这是webpack自带的插件,压缩时可以选择忽略代码中的警告: new webpack.optimize.UglifyJsPlugin({compress: {warnings: false} })
设置全局变量:new webpack.DefinePlugin({TESTENV: JSON.stringify(JSON.parse(process.env.BUILD_TESTENV))}),在应用代码里就可以使用TESTENV了:if(!TESTENV){...},执行命令BUILD_TESTENV=true webpack就可以根据实际情况设置TESTENV了,并且webpack -p会执行uglify dead-code elimination, 任何这种代码都会被剔除, 不用担心打包出不必要/合适的代码。
提取css插件:
在webpack中编写js文件时,可以通过require的方式引入其他的静态资源如css文件,可通过loader对文件自动解析并打包文件。通常会将js 文件打包合并,css文件会在页面的header中嵌入style的方式载入页面。但开发过程中我们并不想将样式打在脚本中,最好可以独立生成css文件,以外链的形式加载。这时extract-text-webpack-plugin插件可以帮我们达到想要的效果,将js中的css文件提取,并以指定的文件名来进行加载。
安装依赖:npm install --save-dev extract-text-webpack-plugin
修改loader和plugins配置:
const ExtractTextPlugin = require("extract-text-webpack-plugin"); // loaders:[ {test: /.css$/, loader: ExtractTextPlugin.extract('style-loader,'css-loader'} ] plugins: [ new ExtractTextPlugin('app.css') ] //也可以分离出多个文件 const extractCSS = new ExtractTextPlugin('stylesheets/[name]-one.css'); const extractLESS = new ExtractTextPlugin('stylesheets/[name]-two.css'); loaders:[ {test: /.css$/, loader: extractCSS.extract('css-loader,'postcss-loader'}, {test: /.less$/, loader: extractLESS.extract('css-loader,'less-loader'} ] plugins: [ extractCSS, extractLESS ]
html-webpack-plugin插件:
(根据指定的模板文件)生成html5文件,并自动插入webpack生成的scripts和css文件文件。
react-transform-hrm插件:
react里结合babel实现热加载HMR(hot module replacement,也就是自动刷新),npm安装插件后,webpack的配置文件plugins中添加:new webpack.HotModuleReplacementPlugin(),配置babel-loader的query选项:
query: { presets: ['es2015', 'react'], plugins: [ ['react-transform', { transforms: [{ transform: 'react-transform-hmr', imports: ['react'], locals: ['module'] }] } ] ] }
分离应用和第三方代码:
当你的应用依赖其他库尤其是像 React JS 这种大型库(或第三方的体积达到整个应用的 20% 或者更高)的时候,你需要考虑把这些依赖分离出去,这样就能够让用户在你更新应用之后不需要再次下载第三方文件。
修改entry和plugins来分离文件:
entry:{ app: path.resolve(__dirname, 'src/app.js'), vendors: ['react', 'react-dom'] } ...... plugins: [ // name指向entry中verdors属性 // filename中的文件会自动构建到output中的path属性下面 new webpack.optimize.CommonsChunkPlugin({name: 'vendors',filename:'vendors.js'}) ]
这样在output.path下会生成两个js文件,bundle.js和vendors.js。
多入口文件下,还可以通过new webpack.optimize.CommonsChunkPlugin('common.js')打包出共用模块,还可以指定共用模块在不同文件里import的次数达到多少时才打包进common.js。