webpack使用
Webpack是一个现代js应用的模块打包机。如果一个文件依赖另一个文件,webpack认为这就存在一个依赖关系。不管另一个文件是什么内容,image,css或js都被当作一个模块。Webpack从entry points开始构建依赖关系图,将应用所需要的所有模块处理成浏览器可识别的格式,再打包成一批(个)文件,将来发送给浏览器。
chunk:多个模块打包之后的代码集合。
使用步骤:
1.全局安装webpack
npm install -g webpack
如果没有package.json,使用CLI创建:npm init
2.项目根目录安装webpack
npm install --save-dev webpack
3.配置webpack.config.js文件,运行webpack命令时不用再输入参数
4.运行webpack
在项目根目录通过CLI调用:$webpack #必须全局安装,否则需要指定webpack.bat的文件路径
webpack默认只会在当前shell工作目录找webpack.config配置文件;如果配置文件不在shell当前工作目录,可以通过--config ./file/pathOfConfig 进行传参数。如果没有配置文件,则需要输入webpack <entry> <output>.
webpack内部自定义了一些参数的映射,运行webpack时可以传入,如--debug映射为debug:true;-p映射为--optimize-minimize
--optimize-occurrence-order.
单双连字符的使用没有特殊规定:Two hyphen-minus characters ( -- ) are used on some programs to specify "long options" where more descriptive option names are used.单连字符取代驼峰命名,用于连接单词或指定很短的参数选项(通常只有一个字母)。
或在Node中调用
var webpack = require('webpack'); //只需要本地安装即可 var config2 = require('./webpack.config'); var compiler = webpack(config2);
webpack的配置文件webpack.config.js
如果该文件存在,只需要执行CLI命令:$webpack,webpack就会自动读取配置,并输出打包的文件。配置文件是以js文件的形式代替命令行形式参数,是符合commonjs规范的node模块,里面可以有function和require(),只要最后以对象形式输出配置即可。
具体配置信息如下:
devtool:source-map.生成何种类型的source map,方便打包后的调试。
entry: webpack从哪里开始构建整个依赖关系。动态的模块不能是entry point。一般一个HTML页面一个entry point。如果有多个entry point,可以采用对象形式定义。不支持通配符配置。如果确实有需要,用如下sinppet,然后将entries传入webpack.config中,或利用node-glob模块读取文件名。
var glob = require("glob"); // Match files using the patterns the shell uses
entry: glob.sync("./src/scripts/*.js")
output:打包后输出的路径(path)和输出的文件名(filename)。可以有多个entry point,但只能有一个output。
如果是多entry,filename需要使用替换保证每个输出文件的名字唯一:
[name] is replaced by the name of the chunk.
[hash] is replaced by the hash of the compilation.(compilation对象的hash值,和webpack的compiler环境相关,同一次编译时的值一样)
[chunkhash] is replaced by the hash of the chunk.(具体模块的hash值,和整个文件内容相关。内容不变,chunkhash值不变)
loaders:webpack将每个资源文件当作一个模块,但是webpack只能处理js。通过loaders将其他格式的资源文件转换成模块后(比如将JSX语言转换成js、将coffeescript转换成js)加入依赖关系中,最后打包输出。需要单独安装包并且在webpack.config.js下的modules关键字下进行配置loaders后才能使用。 loders下的字段:
test:确定哪些文件将被加载器处理;
user:使用哪个加载器,”-loader”后缀不能省略;
include/exclude:必须处理或屏蔽不需要处理的文件(文件夹)(可选);
options:当前loader需要的特殊配置(可选),如babel-loader的.babelrc配置文件里的信息。webpack2.5之前为query
plugins:扩展Webpack功能,会在整个构建过程中生效,执行相关的任务。需要通过require()引入并将插件实例添加到plugins数组中。
resovle:通过别名、扩展名、根路径或备用目录等属性决定webpack如何更快找到import/require()的模块。
resolve.alias:用别的路径或模块替代。把requirejs项目改为webpack项目时可以利用此属性。
resolve.extensions:通过扩展名组成的数组解析require()的模块文件。比如加载一个coffeeScript文件,需要增加’.coffee’扩展名。若修改后必须增加空字符串为第一个元素。
常用命令:webpack –watch改动代码后自动打包
自动刷新页面显示修改后的效果
使用webpack-dev-server模块构建本地服务器:
npm install --save-dev webpack-dev-server
安装完毕之后运行$webpack-dev-server --open
如果报命令不识别的错误,可在package.json的script字段添加("start": "webpack-dev-server --progress --hot --inline --content-base ./dist"),然后运行$npm start命令;
或再全局安装一次。
压缩bundle.js文件的大小
//会加载整个lodash,所以体积更大 import { concat, sortBy, map, sample } from 'lodash'; //Use relative file paths,只加载需要的函数模块,体积更小 //例如import { Button } from 'antd';使用babel-plugin-import插件==》var _button = require('antd/lib/button'); import concat from 'lodash/concat'; import sortBy from 'lodash/sortBy'; import map from 'lodash/map'; import sample from 'lodash/sample';
生产环境中要删除sourcemap
使用 webpack –p
命令构建
bundle.js
通过命令webpack --profile --json >> stats.json生成构建过程的文件,在 https://webpack.github.io/analyse上分析哪个模块占用的空间大。或者使用webpack-bundle-analyzer插件,用视图的形式分析bundle.js中的每个模块。
var debug = process.env.NODE_ENV !== 'production'; //是否是开发模式,生产模式时需要去除sourcemap const webpack = require('webpack'); //访问内置插件 const path = require('path'); var htmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm const config = { devtool: debug ? 'cheap-eval-source-map' : undefined, //开发模式时生成何种类型的源代码映射文件,方便调试打包后的代码 entry: { //webpack打包的切入点,一般一个页面的业务代码定义一个entry point,key为输出chunk的name home: ['./footer.js', './home.js'], //若为[string],则将多个相互独立的文件及其依赖打包成一个chunk about: './about.js', contact: './contact.js', vendor: ['jquery', 'lodash', 'vue'] // 使用commonschunkplugin抽离的公共模块 }, output: { //输出配置项 path: path.resolve(__dirname, 'dist'), //打包后输出的路径 filename: '[name].[chunkhash:8].js', //打包后输出的文件名,多个entry需要替换 chunkFilename: '[id].js', //name of non-entry chunk files。浏览器按需加载的chunk publicPath: 'http://cdnOfCompany.com/' // 供webpack及插件在生产模式下更新内嵌到css、html文件里的cdn路径 }, resolve: { extensions: ['', '.js', '.vue'], fallback: [path.join(__dirname, '../node_modules')], alias: { '@': resolve('src'), //别名,js中import时使用,在src和@import中需要加 ~ 'moment': path.join(__dirname, '../node_modules/moment/min/moment-with-locales.min.js'), // 为模块配置别名 } }, externals: { 'jquery': 'jQuery' // 希望通过script标签引进的依赖,可以require/import,但不打包进bundle。必须先加载,webpack会以module的形式对其引用。 }, module: { // webpack2.0的字段名做了些修改 rules: [//每条rule定义对应的module如何生成,是loaders的alias { test: /\.(js|jsx)$/, //一个匹配loaders所处理的文件的拓展名的正则表达式 exclude: /(node_modules|bower_components)/, use: [ //使用到的loader及其配置 { loader: 'babel-loader', //使用的加载器名称,-loader后缀不能省略;也可通过?query设置参数 options: { //为当前loader设置的参数,对于babel可以提取出单独放在.babelrc文件中; presets: ['react', 'es2015'], plugins: [require('react-html-attrs')] } } ] }, { test: /\.css$/, exclude: /(node_modules)/, //手动添加必须处理(include)或屏蔽不需要处理的文件(文件夹) use: [//使用多个loader处理同一源文件,从右往左顺序处理 //css-loader将css文件当作一个模块引入当前模块中,类名相同样式也不会冲突;style-loader将当前模块中的样式引入页面的style元素中 { loader: 'style-loader' }, { loader: 'css-loader', options: { importLoaders: 1 } }, { loader: 'less-loader', options: { noIeCompat: true } } ] }, { // 解决图片的路径在发布前后不一致的问题(资源打包前在/src/assert和/static中,打包后全部归并到/dist/static目录下) //vue-cli中:assert中的资源要经过webpack处理,只能通过相对路径(视为模块依赖)访问;/static中的资源不用经过webpack处理,通过绝对路径访问 test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, // 小于8kb的直接转为base64 loader: 'url-loader', options: { limit: 8192, name: utils.assetsPath('img/[name].[hash:7].[ext]') // 被加载器处理后重写的url路径,打包之后图片也会被复制到dist的该路径中 } } ] }, plugins: debug ? [] : [//添加插件实例 //依据模板,生成最终的html文件。该文件中会生成标签自动引用了打包后的js/css文件,不用手动一个一个修改url。 new htmlWebpackPlugin({ // 一个单页应用配置一个该实例,多个页面配置多个 filename: 'index.html', //输出文件的名字 template: './src/index.tpl', //使用的模板文件 title: 'this is home page', //传入模版的参数,在模版中可以通过<%=htmlWebpackPlugin.options.title%>获取该值;也可以传递其他自定义的属性供模板使用,如css等静态资源,不会被当做依赖被打包。 favicon: 'path/to/yourfile.ico', //生成的 html 标签中会包含这样一个 link 标签指向favicon inject: 'body', //插入script的位置 chunks: ['vendor', entry], //不设置时默认包含entry中所有的chunks(包括CommonsChunkPlugin插件生成的chunk) excludeChunks: ['contact', 'other chunk']//排除引入的chunk }), new webpack.optimize.DedupePlugin(), new webpack.NoErrorsPlugin(), //构建过程中发生error时跳过错误继续生成 new webpack.optimize.OccurenceOrderPlugin(), new webpack.DefinePlugin({ // webpack定义的全局常量,编译后会替换成value对应的文本,减少手动一个修改 去除对warning和一些提示信息的代码 'process.env': { 'NODE_ENV': JSON.stringify('production') } }), // https://github.com/LiPinghai/UglifyJSDocCN/blob/master/README.md new webpack.optimize.UglifyJsPlugin({ //简化压缩代码,去除console,warning等语句 sourcemap: false, compress: { screw_ie8: true, warnings: false, drop_debugger: true, // 去除debugger语句 pure_funcs: ['console.log'], // 发布时不被打包的函数 dead_code: true // 去除不被执行的代码 }, mangle: { screw_ie8: true }, output: { comments: false, screw_ie8: true } }), // http://webpack.github.io/docs/list-of-plugins.html // https://stackoverflow.com/questions/39548175/can-someone-explain-webpacks-commonschunkplugin new webpack.optimize.CommonsChunkPlugin({ //把所有入口节点的公共代码提取出来,生成一个chunk(name为common.js)进行复用 names: ['vendor', 'manifest'], // 需要抽离的公共模块的名字。配置manifest,其他代码改变但vendor不变化时common.js不变,但manifest的hash值改变(因为含有webpack runtime代码) filename: 'commons.js', // 最后生成公共chunk的文件名,优先于output里的filename配置。不建议固定文件名,如果更新了不方便用户重新下载。 minChunks: Infinity // with more entries, this ensures that no other module goes into the vendor chunk }), new webpack.DllReferencePlugin({ // 引用dll包中的模块,在dll包中无法找到时才打包进当前bundle context: __dirname, manifest: require('./manifest.json') }), // 复制/static目录中的资源到 dist/static下对应的目录中 new CopyWebpackPlugin([ { from: path.resolve(__dirname, '../static'), to: config.build.assetsSubDirectory, // dist/static下 ignore: [] } ]) ], watch: true, //监听源文件的修改,之后recompile,但不刷新页面。为了提高性能,需要将safe write关闭,用save file触发。 watchOptions: { aggregateTimeout: 300, //从修改文件开始到rebuilding的延迟时间,将多个改变积累到一起 poll: 1000, //Check for changes every second ignored: /node_modules///排除监听的目录 }, devServer: { //构建本地服务器实时提供服务并刷新,编译后的文件保存在内存中,所以比较快。 contentBase: './', //本地服务器所提供服务的内容来源 port: 8080, //监听端口,和编辑器的端口不一样 colors: true, //终端中输出结果为彩色 historyApiFallback: true, //所有的跳转将指向index.html inline: true //会输出错误 } }; module.exports = config;
code-spitting
/**每个打包后的bundle都有一个webpack启动函数,负责在客户端启动js;使用commonchunkplugin后runtime函数在最后一个chunk中**/ (function(modules) { // webpackBootstrap
原因:webpack全部打包进一个文件,只要一修改代码,hash就会改变,客户端只能重新下载无法利用缓存。另外有些模块特定的用户不一定用到,首次访问时全部加载会影响首屏速度。
方法:
1. 分离业务逻辑,使用CommonsChunkPlugin打包第三方类库,注意需要在业务逻辑模块之前下载公共模块,因为webpackBootstrap函数会在公共模块中。
<script src="common.js" charset="utf-8"></script> // webpackBootstrap <script src="app.js" charset="utf-8"></script> // webpackJsonp
2. 动态加载指定模块
import()语法:
// other logic import('./sideAd').then(_ => doSomething()).catch(error => 'An error occurred while loading the component'); require.ensure([dependency],function(require){var footAd = require('./footAd') }) // webpack在静态打包时,会将sideAd模块当做单独的chunk打包,将footAd及其依赖单独打包(被webpackJsonp函数包裹),这样当前模块文件体积就会减小
webpack将import('...')转变成``__webpack_require__.e/* import() */(n).then()``*或者``__webpack_require__.e/* require.ensure */(0).then((function (require) {__webpack_require__(n)}))``的形式,在浏览器中webpackbootstrap会*通过__webpack_require__.e(JSONP方法)动态加载chunkId为n的模块。*
require():在webpack项目中只能静态打包到bundle中。依赖都被加载且都被执行后才执行回调函数。
require.ensure():单独打包,动态加载。依赖被下载后不会被执行,只有在回调函数**再次**使用require(module)后,这个模块才会被执行。
3. 使用DllPlugin和DllReferencePlugin
将经常使用的库文件打包到dll包中,它本身不能运行,是用来给业务代码引用的。分离了第三方库,打包速度更快,而且不受业务模块的chunkhash影响。使用步骤:
a. 使用webpack.DllPlugin插件打包第三方库
b. 在主配置文件中使用webpack.DllReferencePlugin插件引用dll包
4. 对于单页应用,可以使用bundle-loader异步加载各路由对应的组件(懒加载),尽快显示首屏。
5. 在vue-router中,使用``const Foo = () => import('./Foo.vue')``懒加载。
routes: [ { path: '/', name: 'Hello', component: () => import('../module/HelloWorld') }, { path: '/check', name: 'check', component: r => require(['@/pages/spa/module/CheckBox'], r) } ]