webpack打包:
在使用webpack打包react项目,每次需要手动编译比较慢,webpack提供了一种热加载的方式,每次变化时自动编译及刷新页面,提升开发效率。同时,在本地开发时使用webpack服务器做代理,做接口mock数据的转发。
1、首先安装webpack相关包:配置package.json文件,使用npm install 安装需要的包
package.json文件
{ "name": "cl_html", "description": "", "main": "index.js", "dependencies": { "babelify": "^6.3.0", "react": "^0.14.8", "react-dom": "^0.14.8", "react-hot-loader": "^1.3.0", "watchify": "^3.4.0" }, "scripts": { "start": "webpack-dev-server", "build": "webpack", "dev": "webpack-dev-server --devtool eval --progress --colors --content-base build" }, "devDependencies": { "babel-core": "^6.17.0", "babel-loader": "^6.2.5", "babel-preset-es2015": "^6.16.0", "babel-preset-react": "^6.16.0", "browser-sync": "^2.11.1", "css-loader": "^0.25.0", "file-loader": "^0.9.0", "gulp": "^3.9.1", "gulp-if": "^2.0.1", "gulp-minify-html": "^1.0.6", "gulp-processhtml": "^1.1.0", "gulp-replace": "^0.5.4", "gulp-rev": "^7.0.0", "gulp-rev-collector": "^1.0.3", "gulp-rev-replace": "^0.4.3", "html-webpack-plugin": "^2.30.1", "react-hot-loader": "^1.3.0", "run-sequence": "^1.2.1", "style-loader": "^0.13.1", "url-loader": "^0.5.7", "webpack": "^1.13.2", "webpack-dev-server": "^1.16.2", "yargs": "^4.7.1", "html-webpack-plugin": "^2.30.1" } }
2、webpack.config.js配置文件:
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); //通过 npm 安装 const webpack = require("webpack"); //访问内置的插件 module.exports = { // 主要有entry,output、module、plugins等配置项 //target:"web", //服务器和浏览器代码都可以用 JavaScript 编写,所以 webpack 提供了多种构建目标(target),你可以在你的 webpack 配置中设置。 默认是web 也可以设置为node //1、入口配置 //单个入口的写法 //entry: path.resolve(__dirname, 'js/app.js'), //简单配置写法 // entry:{ // main:path.resolve(__dirname, 'js/app.js'), // }, //向 entry 属性传入「文件路径(file path)数组」将创建“多个主入口(multi-main entry)” // entry: ['webpack/hot/dev-server', path.resolve(__dirname, 'js/app.js')], //非单页 /*entry:{ index:path.resolve(__dirname, 'js/app.js'), //pageOne、pageTwo作为打包后的filename pageTwo:path.resolve(__dirname, 'js/app2.js') },*/ /* entry:{ pageOne:path.resolve(__dirname, 'js/app1.js'), pageTwo:path.resolve(__dirname, 'js/app2.js'), pageThree:path.resolve(__dirname, 'js/app3.js'), },*/ entry: { index: [ 'webpack-dev-server/client?http://localhost:8099', 'webpack/hot/only-dev-server', './js/index.js' ], common:['react','react-dom'] }, //2 、定义打包后的文件输出 单个入口的打包输出 /*output: { path: path.resolve(__dirname, 'build'), filename: 'index.min.js' },*/ //多入口的打包 output:{ path: path.resolve(__dirname, 'build'), publicPath: "http://localhost:8099/build/", // filename:'[name].min.js', filename: '[name].[hash:6].js', }, //使用CDN和hash /* output: { path: "/home/proj/cdn/assets/[hash]", publicPath: "http://cdn.example.com/assets/[hash]/" },*/ //loader 让 webpack 能够去处理那些非 JavaScript 文件 module: { loaders: [ //在 webpack 的配置中 loader 有两个目标。 // 1、识别出应该被对应的 loader 进行转换的那些文件。(使用 test 属性) // 2、转换这些文件,从而使其能够被添加到依赖图中(并且最终添加到 bundle 中)(use 属性) { test: /.jsx?$/, exclude: /node_modules/, //include exclude 添加必须处理的文件夹或屏蔽不需处理的文件夹 loader: 'babel', //loader的名称 babel的配置项放在babelrc文件里面 query: { presets: ['es2015','react'] } }, { test: /.css$/, loader: 'style!css' }, { test: /.(png|jpg|gif)$/, loader: 'url-loader?limit=8192' // 这里的 limit=8192 表示用 base64 编码 <= 8K 的图像 }, // { // test: /.js?$/, // loaders: ['react-hot', 'babel'], // include: [path.join(__dirname, 'js')] // } ] }, //生成source maps 便于调试 //配置生成Source Maps,选择合适的选项 source-map、cheap-module-source-map、eval-source-map、cheap-module-eval-source-map devtool: 'eval-source-map', //自动刷新配置(1)以命令行方式启动,配置devServer (2)以node 方式启动一个服务 //命令行启动时 本地服务器 让浏览器检测代码的变化,自动刷新 /* devServer: { // contentBase: "./public",//本地服务器所加载的页面所在的目录,默认为根目录 port: 8088, colors: true, //终端中输出结果为彩色 historyApiFallback: true, //不跳转 inline: true, //实时刷新 hot: true //自动刷新 },*/ plugins: [ //版权声明插件:在打包后的文件里添加版权声明 new webpack.BannerPlugin("Copyright PAS Frontend."), // 设置生产环境全局变量,以便告诉所有类库,过滤不必要的代码 new webpack.DefinePlugin({ 'process.env': { 'NODE_ENV': "production" } }), // 压缩代码,去掉评论注释等 减少文件体积 new webpack.optimize.UglifyJsPlugin({ output: { comments: false, }, compress: { warnings: false } }), //压缩 //每次打包后index.html中的路径也会自动加上hash值 new HtmlWebpackPlugin({ template: path.resolve(__dirname, './index.html'), filename:path.resolve(__dirname, './index.build.html'), //js路径替换成带hash的 hash:true, title:"标题" }), // 拆分插件: 打包成不同文件 new webpack.optimize.CommonsChunkPlugin({ name: 'common', // 上面入口定义的节点组 filename: 'build-common.js' //最后生成的文件名 }), //热加载插件HMR // new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(), // new webpack.NoErrorsPlugin() ] }
(1)entry配置项:
//单个入口的写法
entry: path.resolve(__dirname, 'js/app.js'), //简单配置写法
//对象形式传入,配置多个入口文件
entry:{ index:path.resolve(__dirname, 'js/app.js'), //index、pageTwo作为output打包后的filename pageTwo:path.resolve(__dirname, 'js/app2.js') }
//向 entry 属性传入「文件路径(file path)数组」将创建“多个主入口(multi-main entry)”
// entry: ['webpack/hot/dev-server', path.resolve(__dirname, 'js/app.js')],
(2)多入口的打包的输出配置,如果需要,filename可以配置参数,加上hash。
publicPath来指定编译后的包(bundle)的访问位置。另外,打包后的文件加上随机字符串后,在index.html中引入的脚本路径也需要动态调整,这个publicPath会作为脚本的根路径
output:{ publicPath: "http://localhost:8099/build/", path: path.resolve(__dirname, 'build'), filename: '[name].[hash:6].js', //name就是entry中配置的键名,hash用于指定生成随机6为随机字符 }
(3)module 主要处理一些es6编译、css预处理、图片处理等
//使用loaders对模块进行各种处理,如将es7es6转成es5
module: { loaders: [{ test: /.jsx?$/, //匹配要处理文件的扩展名 (必选) exclude: /node_modules/, //include exclude 添加必须处理的文件夹或屏蔽不需处理的文件夹 loader: 'babel', //loader的名称 babel的配置项放在babelrc文件里面 // query: { //为loader提供额外的设置选项。 //babel配置选项可以写在.babelrc文件中 // presets: ['es2015','react'] //es2015 解析es6、 react 解析react中的jsx // } }, { test: /.css$/, // loader: 'style!css' //style-loader css-loader /* loader: 'style!css?modules' //css module*/ loader: 'style!css?modules!postcss' }, { test: /.(png|jpg|gif)$/, loader: 'url-loader?limit=8192' // 这里的 limit=8192 表示用 base64 编码 <= 8K 的图像 }, //json文件处理 { test: /.json$/, loader: "json" } ] },
(4)plugins:webpack插件,压缩、热加载、拆分打包等,有些插件是webpack自带的可以直接使用,如BannerPlugin、UglifyJsPlugin等,但有些如HtmlWebpackPlugin需要自己安装 (npm install html-webpack-plugin --save-dev)
plugins: [ //版权声明插件:在打包后的文件里添加版权声明 new webpack.BannerPlugin("Copyright PAS Frontend."), // 设置生产环境全局变量,以便告诉所有类库,过滤不必要的代码 new webpack.DefinePlugin({ 'process.env': { 'NODE_ENV': "production" } }), // 压缩代码,去掉评论注释等 减少文件体积 new webpack.optimize.UglifyJsPlugin({ output: { comments: false, }, compress: { warnings: false } }), //压缩 //每次打包后index.html中的路径也会自动加上hash值 new HtmlWebpackPlugin({ template: path.resolve(__dirname, './index.html'), filename:path.resolve(__dirname, './index.build.html'), //js路径替换成带hash的 hash:true, title:"标题" }), // 拆分插件: 打包成不同文件 new webpack.optimize.CommonsChunkPlugin({ name: 'common', // 上面入口定义的节点组 filename: 'build-common.js' //最后生成的文件名 }), //热加载插件HMR // new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(), // new webpack.NoErrorsPlugin() ]
3、热加载 webpack-dev-server
webpack-dev-server是一个小型的Node.js Express服务器,它使用webpack-dev-middleware来服务于webpack的包,除此自外,它还有一个通过Sock.js来连接到服务器的微型运行时.
安装使用 npm install webpack-dev-server
(1)启动一个webpack-server服务,使用该服务访问项目文件,如http://localhost:8099/index.html,后续再修改脚本时,不需要手动执行webpack打包,webpack-server会自动监听变化并刷新页面。
webpack.server.js文件,使用node webpack.server.js启动服务 var webpack = require('webpack'); var WebpackDevServer = require('webpack-dev-server'); var config = require('./webpack.config'); new WebpackDevServer(webpack(config), { publicPath: config.output.publicPath, hot: true, historyApiFallback: false, proxy: { //接口转发 "/restapi/hkstock": "http://localhost:3009/" }, watchOptions: { aggregateTimeout: 300, poll: 1000 }, }).listen(8099, 'localhost', function (err, result) { if (err) console.log(err); console.log('Listening at localhost:8099'); });
(2)webpack.config.js配置,配置entry、output,同时由于加入hash,index.html中的路径也需要动态改变。因此引入 HtmlWebpackPlugin插件,动态生成一个index.html,修改里面的js路径。
entry: { index: [ 'webpack-dev-server/client?http://localhost:8099', //监听该服务下的变化 'webpack/hot/only-dev-server', './js/index.js' ] }, output:{ path: path.resolve(__dirname, 'build'), publicPath: "http://localhost:8099/build/", // filename:'[name].min.js', filename: '[name].[hash:6].js', }, plugins:[ //每次打包后index.html中的路径也会自动加上hash值 new HtmlWebpackPlugin({ template: path.resolve(__dirname, './index.html'), filename:path.resolve(__dirname, './index.build.html'), //js路径替换成带hash的 hash:true, title:"标题" }), //热加载插件HMR new webpack.HotModuleReplacementPlugin(), ]
4、提取公共模块
参考:https://segmentfault.com/a/1190000011920743
如果文件是多入口的文件,可能存在,重复代码,把公共代码提取出来,又不会重复下载公共代码了(多个页面间会共享此文件的缓存)。
对于SPA 应用来说没有特别的需要分离出模块,但是针对首屏渲染速度的提升,可以将某些独立模块分离出来实现按需加载。
// CommonsChunkPlugin的初始化常用参数有解析?
// name: 这个给公共代码的chunk唯一的标识
// filename,如何命名打包后生产的js文件,也是可以用上[name]、[hash]、[chunkhash]
// minChunks,公共代码的判断标准:某个js模块被多少个chunk加载了才算是公共代码
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename:'vendor.min.js', minChunks: function (module, count) { // any required modules inside node_modules are extracted to vendor return ( module.resource && /.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, '../node_modules') ) === 0 ) } }),
5、代码分割
单页面应用的公共模块没有必要提取出单独的文件,因为不必考虑复用的情况。但是对于打包生成的文件过大,我们又想分离出几个模块有需要的时候才加载,其实这并不是提取公共模块,而是代码分割,通过:
new webpack.optimize.CommonsChunkPlugin({
name: ['生成的项目公共模块文件名', '第三方模块文件名'],
minChunks: 2,
}),
参考: