Webpack Loader种类以及执行顺序
我们在用webpack构建项目的时候,有两种配置打包文件的方式:
- import或者require :a-loader!b-loader!.././static/dog.png(打包某一个文件)
- 配置webpack.config.js文件的module.rules(打包某一类的文件)
针对于以上的第二种方式我贴下我之前一篇博客中的配置 Vue动态注册异步组件(非同一个工程的组件)
var path = require('path') var webpack = require('webpack') module.exports = { entry: process.NODE_ENV === 'development' ? './src/main.js' : './src/component/index.js', output: { path: path.resolve(__dirname, './dist'), publicPath: '/dist/', filename: 'async-component.js' }, module: { rules: [ { test: /\.css$/, use: [ 'vue-style-loader', 'css-loader' ], }, { test: /\.scss$/, use: [ 'vue-style-loader', 'css-loader', 'sass-loader' ], }, { test: /\.sass$/, use: [ 'vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax' ], }, { test: /\.vue$/, loader: 'vue-loader', options: { loaders: { // Since sass-loader (weirdly) has SCSS as its default parse mode, we map // the "scss" and "sass" values for the lang attribute to the right configs here. // other preprocessors should work out of the box, no loader config like this necessary. 'scss': [ 'vue-style-loader', 'css-loader', 'sass-loader' ], 'sass': [ 'vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax' ] } // other vue-loader options go here } }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }, { test: /\.(png|jpg|gif|svg)$/, loader: 'file-loader', options: { name: '[name].[ext]?[hash]' } } ] }, resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' }, extensions: ['*', '.js', '.vue', '.json'] }, devServer: { historyApiFallback: true, noInfo: true, overlay: true }, performance: { hints: false }, devtool: '#eval-source-map' } if (process.env.NODE_ENV === 'production') { module.exports.devtool = '#source-map' // http://vue-loader.vuejs.org/en/workflow/production.html module.exports.plugins = (module.exports.plugins || []).concat([ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"production"' } }), // new webpack.optimize.UglifyJsPlugin({ // sourceMap: true, // compress: { // warnings: false // } // }), new webpack.LoaderOptionsPlugin({ minimize: false }) ]) }
对单文件打包的方式的loader被称为行内(inline)loader;对于rules中的loader,webpack还定义了一个属性 enforce,可取值有 pre(为pre loader)、post(为post loader),如果没有值则为(normal loader)。所以loader在webpack中有4种:normal,inline,pre,post。
贴下官方源码地址 :Loader Type
for (const r of result) { if (r.type === "use") { if (r.enforce === "post" && !noPrePostAutoLoaders) { useLoadersPost.push(r.value); } else if ( r.enforce === "pre" && !noPreAutoLoaders && !noPrePostAutoLoaders ) { useLoadersPre.push(r.value); } else if ( !r.enforce && !noAutoLoaders && !noPrePostAutoLoaders ) { useLoaders.push(r.value); } } else if ( typeof r.value === "object" && r.value !== null && typeof settings[r.type] === "object" && settings[r.type] !== null ) { settings[r.type] = cachedCleverMerge(settings[r.type], r.value); } else { settings[r.type] = r.value; } }
所有的loader的执行顺序都有两个阶段:pitching和normal阶段,类似于js中的事件冒泡、捕获阶段(有人嫌官方的词描述的比较晦涩,修改为loader的标记阶段(mark stage)和执行阶段(execution/run stage))。
对于第二种方式的解释:webpack 官方解释
- Pitching阶段: post,inline,normal,pre
- Normal阶段:pre,normal,inline,post
我看有大神的解释如下:(很好理解)
It's like the two phases of event bubbling...
a!b!c!resource
pitch a
pitch b
pitch c
read file resource (adds resource to dependencies)
run c
run b
run a
When a loader return something in the pitch phase the process continues with the normal phase of the next loader... Example:
pitch a
pitch b (returns something)
run a
对应的方式1解析loader的源码:
let elements = requestWithoutMatchResource .replace(/^-?!+/, "") .replace(/!!+/g, "!") .split("!");
对应的方式2解析loader的源码:
const result = this.ruleSet.exec({ resource: resourcePath, realResource: matchResource !== undefined ? resource.replace(/\?.*/, "") : resourcePath, resourceQuery, issuer: contextInfo.issuer, compiler: contextInfo.compiler });
那么问题来了,如果我们采用了两种解析loader的方式,他们的执行是什么样的呢?答案是inline loader优先级高于config配置文件中的loader:源码
webpack使用了neo-async库(用来提供js异步编程的工具库)来解析loader模块,解析inline loader的源码,
解析config loader的源码
webpack官方文档推荐不适用inline loader,最好用在配置文件中使用loader(注意:loader处理的代码中是含有inline loader的)。另外,在特殊情况下我我们可以在inline loader间接改变loader的执行顺序(禁止某些另外3种loader),比如在我们的自己公司某个同事的不是很规范的js库在引入的时候需要禁止掉eslint-loader对其进行处理
- 加入 ! 前缀禁用配置文件中的普通loader,比如:
require("!raw!./script.coffee")
- 加入 !! 前缀禁用配置文件中所有的loader,比如:
require("!!raw!./script.coffee")
- 加入 -! 前缀禁用配置文件中的pre loader和普通loader,但是不包括post loader,比如:
require("!!raw!./script.coffee")
关于loader的禁用,webpack官方的建议是:除非从另一个loader处理生成的,一般不建议主动使用
- pre loader 配置:图片压缩
- 普通loader 配置:coffee-script转换
- inline loader 配置:bundle loader
- post loader 配置: 代码覆盖率工具