webpack
什么是webpack
webpack是一个模块打包器,包(bundle)就是一个js文件,它把一堆资源合并在一起,以便在同一个文件请求中发回给客户端,webpack还能处理一些浏览器不能直接运行的拓展语言,如Scss,typeScript等。webpack是基于配置型的构建工具,它把整个项目作为一个整体,通过一个给定的主文件(如index.js),从这个主文件开始找到项目所有的依赖文件,使用loaders处理它们,最终打包为一个或多个浏览器可识别的js文件。
webpack和gulp的区别
webpack:作为一个智能解析器,几乎可以处理任何一个第三方的库,无论是commonjs,amd还是普通的js文件,甚至加载依赖的时候可以动态表达式。webpack是一种预编译的解决方案,不需要在浏览器中加载解释器,而且不管是AMD/CMD/ES6风格的模块化,它都认识,并且能编译成浏览器认识的JS。webpack是一种模块化的解决方案,webpack在很多场景下可以替代gulp/grunt类的工具
gulp:在一个配置文件中,指明对某些文件进行类似编译、组合、压缩等任务的具体步骤,工具之后可以在自动替你完成这些任务。gulp是一个能够优化前端的开发流程的工具。
使用webpack
项目环境
//全局安装 npm install -g webpack //安装到项目里 npm install --save-dev webpack
如果项目中没有package.json这个文件,可以通过以下指令创建:
npm init
一个简单的配置文件
webpack.config.js
// 一个常见的`webpack`配置文件 const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { entry: __dirname + "/app/main.js", //已多次提及的唯一入口文件 output: { path: __dirname + "/build", //打包后的文件存放的地方 filename: "bundle-[hash].js" //打包后输出文件的文件名 }, devtool: 'none', devServer: { contentBase: "./public", //本地服务器所加载的页面所在的目录 historyApiFallback: true, //不跳转 inline: true, hot: true }, module: { rules: [{ test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ }, { test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: [{ loader: "css-loader", options: { modules: true, localIdentName: '[name]__[local]--[hash:base64:5]' } }, { loader: "postcss-loader" }], }) } } ] }, plugins: [ new webpack.BannerPlugin('版权所有,翻版必究'), new HtmlWebpackPlugin({ template: __dirname + "/app/index.tmpl.html" //new 一个这个插件的实例,并传入相关的参数 }), new webpack.optimize.OccurrenceOrderPlugin(), new webpack.optimize.UglifyJsPlugin(), new ExtractTextPlugin("style.css") ] };
配置分析
一 entry和output
entry用来定义入口文件,可以是字符串或数组或对象
1.字符串:
module.exports = { entry: './main.js' };
2.数组:除了main.js作为入口js之外,还有一个是用来配置webpack提供的一个静态资源服务器webpack-dev-server,它会监控项目中每个文件的变化,实时构建,并自动刷新页面。
module.exports = { entry: [ 'webpack/hot/only-dev-server', './main.js' ] };
3.对象:将不同的文件按需构建,按需使用
module.exports = { entry: { a: './a.js', b: './b.js' } };
output用于定义构建后的文件的输出,是个对象,其中包含了path和filename
module.exports = { output: { path: './build', filename: 'bundle.js' } };
二 devtool
devtool属性可以配置调试代码的方式,有多种调试方式,一般只在开发时使用,生产环境下应该将值设置为false。常用值有两个:
eval: 可以设断点调试,不显示信息,每个js模块代码用eval()执行,并且在生成的每个模块代码尾部加上注释,不 会生成.map文件。
source-map: 可以设断点调试,不显示列信息,生成相应的.Map文件,并在合并后的代码尾部加上注释//# sourceMappingURL=**.js.map,可以看到模块代码并没有被eval()包裹,此种模式并没有将调试信息放入打 包后的代码中,保持了打包后代码的简洁性.
devtool选项 | 配置结果 |
source-map | 在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的source map,但是它会减慢打包速度; |
cheap-module-source-map | 在一个单独的文件中生成一个不带列映射的map,不带列映射提高了打包速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便; |
eval-source-map | 使用eval打包源文件模块,在同一个文件中生成干净的完整的source map。这个选项可以在不影响构建速度的前提下生成完整的sourcemap,但是对打包后输出的JS文件的执行具有性能和安全的隐患。在开发阶段这是一个非常好的选项,在生产阶段则一定不要启用这个选项; |
cheap-module-eval-source-map | 这是在打包文件时最快的生成source map的方法,生成的Source Map 会和打包后的JavaScript文件同行显示,没有列映射,和eval-source-map选项具有相似的缺点; |
上述选项由上到下打包速度越来越快,不过同时也具有越来越多的负面作用,较快的打包速度的后果就是对打包后的文件的的执行有一定影响。cheap-module-eval-source-map推荐在大型项目考虑时间成本时使用,对小到中型的项目中,eval-source-map是一个很好的选项。
三 devserver
如果想让你的浏览器监听你的代码修改,并自动刷新显示修改后的结果,webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js构建,可以实现你想要的这些功能,不过这是一个单独的组件,在webpack进行配置之前需要单独安装它作为项目依赖。
npm install --save-dev webpack-dev-server
devserver有一些配置选项,可以参考如下的配置:
devserver的配置选项 | 功能描述 |
contentBase | 默认webpack-dev-server会为根文件夹提供本地服务器,如果想为另外一个目录下的文件提供本地服务器,应该在这里设置其所在目录 |
port | 设置默认监听端口,如果省略,默认为”8080“ |
inline | 设置为true,当源文件改变时会自动刷新页面 |
historyApiFallback | 在开发单页应用时非常有用,它依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html |
module.exports = { devtool: 'eval-source-map', entry: __dirname + "/app/main.js", output: { path: __dirname + "/public", filename: "bundle.js" }, devServer: { contentBase: "./public",//本地服务器所加载的页面所在的目录 historyApiFallback: true,//不跳转 inline: true//实时刷新 } }
四 Babel
Babel是一个编译JavaScript的平台,它可以编译代码帮你达到以下目的:
- 让你能使用最新的JavaScript代码(ES6,ES7),而不用管新标准是否被当前使用的浏览器完全支持;
- 让你能使用基于JavaScript进行拓展的语言,如React的JSX;
Babel其实是几个模块化的包,其核心功能位于称为babel-core的npm包中,webpack可以把其不同的包整合在一起使用,对于每一个你需要的功能或拓展,你都需要安装单独的包(用得最多的是解析Es6的babel-env-preset包和解析JSX的babel-preset-react包)。
module.exports = { entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件 output: { path: __dirname + "/public",//打包后的文件存放的地方 filename: "bundle.js"//打包后输出文件的文件名 }, devtool: 'eval-source-map', devServer: { contentBase: "./public",//本地服务器所加载的页面所在的目录 historyApiFallback: true,//不跳转 inline: true//实时刷新 }, module: { rules: [ { test: /(\.jsx|\.js)$/, use: { loader: "babel-loader", options: { presets: [ "env", "react" ] } }, exclude: /node_modules/ } ] } };
现在你的webpack的配置已经允许你使用ES6以及JSX的语法了。
五 module
module主要是用来配置加载器(loaders)。webpack本身只能处理javascript模块,如果要处理其他类型的文件,就需要使用loader进行转换,每一种文件会用不同的加载器处理。
loadres,preLoadres,postLoaders的配置主要包括以下几项:
- test:一个匹配loaders所处理的文件的拓展名的正则表达式(必须)
- loader: loader的名称(必须)
- include/exclude: 手动添加必须处理的文件/文件夹,或屏蔽不需要处理的文件/文件夹(可选)
- query: 为loaders提供额外的设置选项(可选)
module: { exprContextCritical: false, rules: [ { test: /\.ts$/, enforce: 'pre', loader: 'tslint-loader', exclude: /target/ }, { test: /\.tsx?$/, use: [ { loader: 'babel-loader', options: { cacheDirectory: './.babelcache/', plugins: ['angularjs-annotate'] } } ], enforce: 'post', exclude: createBabelExclude() }, { test: /\.json$/, loader: 'json-loader' }, { test: /\.html$/, loader: 'html-loader' }, { test: require.resolve('highcharts'), loader: 'expose-loader?Highcharts' } ] }
六 resolve
resolve用来配置文件路径的指向。可以定义文件跟模块的默认路径及后缀等,节省webpack搜索文件的时间、优化引用模块时的体验。常用的包括alias,extensions,root,moduleDirectories属性。
- alias: 是个对象,把资源路径重定向到另一个路径
- extensions: 是个数组,定义资源的默认后缀,比如定义后引用a.js、b.json、c.css等资源可以不用写后缀名直接写a、b、c
- root: 是个数组,通过绝对路径的方式来定义查找模块的文件夹。可以是一个数组,主要是用来增加模块的搜寻位置使用的。root 的值必须是绝对路径,使用path.resolve设置。
- modulesDirectories: 是个数组,用来设置搜索的目录名的,默认为["web_modules", "node_modules"]
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' } }
七 plugins
插件是用来拓展webpack的功能的,会在整个构建过程中生效,执行相关的任务。Loadres和Plugins是完全不同的,loaders是在打包构建过程中用来处理源文件的(JSX,Scss,Less),一次处理一个,插件并不是直接操作单个文件,它直接对整个构建过程起作用。webpack有很多内置插件和第三方插件。使用某个插件,先用npm安装,然后在webpack配置的plugins关键字部分添加该插件的一个实例。
比如常用的HtmlWebpackPlugin,这个插件的作用是依据一个简单的index.html模板,生成一个自动引用你打包后的JS文件的新index.html。这在每次生成的js文件名称不同时非常有用(比如添加了hash值)。
安装:
npm install --save-dev html-webpack-plugin
这个插件自动完成了我们之前手动做的一些事情,在正式使用之前需要对一直以来的项目结构做一些更改:
(1)移除public文件夹,利用此插件,index.html文件会自动生成,此外CSS已经通过前面的操作打包到JS中了。
(2)在app目录下,创建一个index.tmpl.html文件模板,这个模板包含title等必须元素,在编译过程中,插件会依据此模板生成最终的html页面,会自动添加所依赖的css,js,favicon等文件.
index.tmpl.html中的模板源代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Webpack Sample Project</title> </head> <body> <div id='root'> </div> </body> </html>
(3)新webpack的配置文件,方法同上,新建一个build文件夹用来存放最终的输出文件
const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件 output: { path: __dirname + "/build", filename: "bundle.js" }, devtool: 'eval-source-map', devServer: { contentBase: "./public",//本地服务器所加载的页面所在的目录 historyApiFallback: true,//不跳转 inline: true//实时刷新 }, module: { rules: [ { test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ }, { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader", options: { modules: true } }, { loader: "postcss-loader" } ] } ] }, plugins: [ new webpack.BannerPlugin('版权所有,翻版必究'), new HtmlWebpackPlugin({ template: __dirname + "/app/index.tmpl.html"//new 一个这个插件的实例,并传入相关的参数 }) ], };
执行npm start,可以发现build文件夹下面生成了bundle.js和index.html
介绍一些常用的插件:
插件 | 作用 |
extract-text-webpack-plugin | 第三方插件,将 CSS 打包成独立文件,而非内联。 |
HotModuleReplacementPlugin | 代码热替换 |
HtmlWebpackPlugin | 生成 HTML 文件,配合 ExtractTextPlugin 可以加入打包后的 js 和 css。 |
NoErrorsPlugin | 报错但不退出 webpack 进程。 |
DefinePlugin | 主要用来定义全局的环境变量,以便我们在自己的程序中引用它。 |
代码热替换
代码热替换在开发的时候无需刷新页面即可看到更新,只在开发环境的配置文件使用,具体配置如下。
npm install --save-dev webpack-dev-server webpack-dev-middleware express
把webpack/hot/dev-server加入到 webpack 配置文件的 entry 项:
<!-- webpack.config.dev.js --> entry: [ 'webpack-dev-server/client?http://localhost:3000', './src/app.js' ],
把new webpack.HotModuleReplacementPlugin()加入到 webpack 配置文件的 plugins 项:
<!-- webpack.config.js --> plugins: [ new webpack.HotModuleReplacementPlugin() ]
在项目根目录新建个server.js文件,将server部分分离到一个单独的 :
<!-- server.js --> var webpack = require('webpack'); var WebpackDevServer = require('webpack-dev-server'); var config = require('./webpack.config'); var compiler = webpack(config); var server = new WebpackDevServer(compiler, { publicPath: config.output.publicPath, hot: true, historyApiFallback: true, stats: { colors: true, hash: false, timings: true, chunks: false, chunkModules: false, modules: false } }); server.listen(3000, 'localhost', function(err, result) { if (err) { return console.log(err); } console.log('Listening at http://localhost:3000/'); });
在 package.json 中定义启动监听热加载:
<!-- package.json --> "scripts": { "start": "node server.js" }
现在你可以通过运行npm start启动服务器。