Webpack配置全解析(基础篇)

  Webpack凭借强大的功能,成为最流行和最活跃的打包工具,也是面试时高级程序员必须掌握的“软技能”;笔者结合在项目中的使用经验,介绍webpack的使用;本文是入门篇,主要介绍webpack的入口、输出和各类loader、plugins的使用以及开发环境的搭建,技术大佬请ctrl+w。css

  本文全部的demo代码均在WebpackDemohtml

概念

  来看一下官网对webpack的定义:前端

本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序须要的每一个模块,而后将全部这些模块打包成一个或多个bundle。vue

  首先webpack是一个静态模块打包器,所谓的静态模块,包括脚本、样式表和图片等等;webpack打包时首先遍历全部的静态资源,根据资源的引用,构建出一个依赖关系图,而后再将模块划分,打包出一个或多个bundle。再次白piao一下官网的图,生动的描述了这个过程:node

logo.png

  提到webpack,就不得不提webpack的四个核心概念jquery

  • 入口(entry):指示 webpack 应该使用哪一个模块,来做为构建其内部依赖图的开始
  • 输出(output):在哪里输出它所建立的 bundles
  • loader:让 webpack 可以去处理那些非 JavaScript 文件
  • 插件(plugins):用于执行范围更广的任务

你的第一个打包器

  咱们首先在全局安装webpack:webpack

npm install webpack webpack-cli –g
复制代码

  webpack能够不使用配置文件,直接经过命令行构建,用法以下:git

webpack <entry> [<entry>] -o <output>
复制代码

  这里的entry和output就对应了上述概念中的入口和输入,咱们来新建一个入口文件:程序员

//demo1/index.js
var a = 1
console.log(a)
document.write('hello webpack')
复制代码

  有了入口文件咱们还须要经过命令行定义一下输入路径dist/bundle.js:es6

webpack index.js -o dist/bundle.js
复制代码

  这样webpack就会在dist目录生成打包后的文件。

demo1-result.png

  咱们也能够在项目目录新建一个html引入打包后的bundle.js文件查看效果。

配置文件

  命令行的打包方式仅限于简单的项目,若是咱们的项目较为复杂,有多个入口,咱们不可能每次打包都把入口记下来;所以通常项目中都使用配置文件来进行打包;配置文件的命令方式以下:

webpack [--config webpack.config.js]
复制代码

  配置文件默认的名称就是webpack.config.js,一个项目中常常会有多套配置文件,咱们能够针对不一样环境配置不一样的文件,经过--config来进行切换:

//生产环境配置
webpack --config webpack.prod.config.js
//开发环境配置
webpack --config webpack.dev.config.js
复制代码

多种配置类型

  config配置文件经过module.exports导出一个配置对象:

//webpack.config.js
var path = require('path');
module.exports = {
  mode: 'development',
  //入口文件
  entry: './index.js',
  //输出目录
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  }
};
复制代码

  除了导出为对象,还能够导出为一个函数,函数中会带入命令行中传入的环境变量等参数,这样能够更方便的对环境变量进行配置;好比咱们在打包线上正式环境和线上开发环境能够经过env进行区分:

var path = require('path');
//env:环境对象
module.exports = function(env, argv){
  return {
    //其余配置
    entry: './index.js',
    output: {}
  }
};
复制代码

  另外还能够导出为一个Promise,用于异步加载配置,好比能够动态加载入口文件:

module.exports = () => {
    return new Promise((resolve, reject)=>{
        setTimeout(()=>{
            resolve({
                entry: './index.js',
                output: {}
            })
        }, 5000)
    })
}
复制代码

入口

  正如在上面提到的,入口是整个依赖关系的起点入口;咱们经常使用的单入口配置是一个页面的入口:

module.exports = {
    entry: './index.js',
}
复制代码

  它是下面的简写:

module.exports = {
    entry: {
        main: './index.js'
    },
}
复制代码

  可是咱们一个页面可能不止一个模块,所以须要将多个依赖文件一块儿注入,这时就须要用到数组了,代码在demo2中:

module.exports = {
    entry: [
        //轮播图模块
        './src/banner.js',
        //主模块
        './src/index.js', 
        //底部模块
        './src/foot.js'
    ],
}
复制代码

  有时候咱们一个项目可能有不止一个页面,须要将多个页面分开打包,entry支持传入对象的形式,代码在demo3中:

//demo3
module.exports = {
    entry: {
        home: './src/home.js',
        list: './src/list.js',
        detail: ['./src/detail.js', './src/common.js'],
    },
}
复制代码

  这样webpack就会构建三个不一样的依赖关系。

输出

  output选项用来控制webpack如何输入编译后的文件模块;虽然能够有多个entry,可是只能配置一个output

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
        //CDN地址
        publicPath: '/',
    },
}
复制代码

  这里咱们配置了一个单入口,输出也就是bundle.js;可是若是存在多入口的模式就行不通了,webpack会提示Conflict: Multiple chunks emit assets to the same filename,即多个文件资源有相同的文件名称;webpack提供了占位符来确保每个输出的文件都有惟一的名称:

module.exports = {
    entry: {
        home: './src/home.js',
        list: './src/list.js',
        detail: ['./src/detail.js', './src/common.js'],
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].bundle.js',
    },
}
复制代码

  这样webpack打包出来的文件就会按照入口文件的名称来进行分别打包生成三个不一样的bundle文件;还有如下不一样的占位符字符串:

占位符 描述
[hash] 模块标识符(module identifier)的 hash
[chunkhash] chunk 内容的 hash
[name] 模块名称
[id] 模块标识符
[query] 模块的 query,例如,文件名 ? 后面的字符串

  在这里引入Module、Chunk和Bundle的概念,上面代码中也常常会看到有这两个名词的出现,那么他们三者到底有什么区别呢?首先咱们发现module是常常出如今咱们的代码中,好比module.exports;而Chunk常常和entry一块儿出现,Bundle老是和output一块儿出现。

  • module:咱们写的源码,不管是commonjs仍是amdjs,均可以理解为一个个的module
  • chunk:当咱们写的module源文件传到webpack进行打包时,webpack会根据文件引用关系生成chunk文件,webpack 会对这些chunk文件进行一些操做
  • bundle:webpack处理好chunk文件后,最后会输出bundle文件,这个bundle文件包含了通过加载和编译的最终源文件,因此它能够直接在浏览器中运行。

  咱们经过下面一张图更深刻的理解这三个概念:

chunk-bundle.png

总结:

  module,chunk 和 bundle 其实就是同一份逻辑代码在不一样转换场景下的取了三个名字:咱们直接写出来的是module,webpack处理时是chunk,最后生成浏览器能够直接运行的bundle。

hash、chunkhash、contenthash

  理解了chunk的概念,相信上面表中chunkhash和hash的区别也很容易理解了;

  • hash:是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值都会更改,而且所有文件都共用相同的hash值。
  • chunkhash:跟入口文件的构建有关,根据入口文件构建对应的chunk,生成每一个chunk对应的hash;入口文件更改,对应chunk的hash值会更改。
  • contenthash:跟文件内容自己相关,根据文件内容建立出惟一hash,也就是说文件内容更改,hash就更改。

模式

  在webpack2和webpack3中咱们须要手动加入插件来进行代码的压缩、环境变量的定义,还须要注意环境的判断,十分的繁琐;在webpack4中直接提供了模式这一配置,开箱便可用;若是忽略配置,webpack还会发出警告。

module.exports = {
  mode: 'development',
};
//至关于
module.exports = {
   devtool:'eval',
   plugins: [
      new webpack.NamedModulesPlugin(),
      new webpack.NamedChunksPlugin(),
      new webpack.DefinePlugin({ 
        "process.env.NODE_ENV": JSON.stringify("development") 
      })
   ]
}
复制代码

  开发模式是告诉webpack,我如今是开发状态,也就是打包出来的内容要对开发友好,便于代码调试以及实现浏览器实时更新。

module.exports = {
  mode: 'production',
};
//至关于
module.exports = {
   plugins: [
      new UglifyJsPlugin(/*...*/),
      new webpack.DefinePlugin({ 
        "process.env.NODE_ENV": JSON.stringify("production") 
      }),
      new webpack.optimize.ModuleConcatenationPlugin(),
      new webpack.NoEmitOnErrorsPlugin()
   ]
}
复制代码

  生产模式不用对开发友好,只须要关注打包的性能和生成更小体积的bundle。看到这里用到了不少Plugin,不用慌,下面咱们会一一解释他们的做用。

  相信不少童鞋都曾有过疑问,为何这边DefinePlugin定义环境变量的时候要用JSON.stringify("production"),直接用"production"不是更简单吗?

  咱们首先来看下JSON.stringify("production")生成了什么;运行结果是""production"",注意这里,并非你眼睛花了或者屏幕上有小黑点,结果确实比"production"多嵌套了一层引号。

  咱们能够简单的把DefinePlugin这个插件理解为将代码里的全部process.env.NODE_ENV替换为字符串中的内容。假如咱们在代码中有以下判断环境的代码:

//webpack.config.js
module.exports = {
  plugins: [
    new webpack.DefinePlugin({ 
      "process.env.NODE_ENV": "production"
    }),
  ]
}
//src/index.js
if (process.env.NODE_ENV === 'production') {
    console.log('production');
}
复制代码

  这样生成出来的代码就会编译成这样:

//dist/bundle.js
//代码中并无定义production变量
if (production === 'production') {
    console.log('production');
}
复制代码

  可是咱们代码中可能并无定义production变量,所以会致使代码直接报错,因此咱们须要经过JSON.stringify来包裹一层:

//webpack.config.js
module.exports = {
  plugins: [
    new webpack.DefinePlugin({ 
      //"process.env.NODE_ENV": JSON.stringify("production")
      //至关于
      "process.env.NODE_ENV": '"production"'
    }),
  ]
}
//dist/bundle.js
if ("production" === 'production') {
    console.log('production');
}
复制代码

  这样编译出来的代码就没有问题了。

自动生成页面

  在上面的代码中咱们发现都是手动来生成index.html,而后引入打包后的bundle文件,可是这样太过繁琐,并且若是生成的bundle文件引入了hash值,每次生成的文件名称不同,所以咱们须要一个自动生成html的插件;首先咱们须要安装这个插件:

npm install --save-dev html-webpack-plugin
复制代码

  在demo3中,咱们生成了三个不一样的bundle.js,咱们但愿在三个不一样的页面能分别引入这三个文件,以下修改config文件:

module.exports = {
    //省略其余代码
    plugins: [
        new HtmlWebpackPlugin({
            //引用的模板文件
            template: './index.html',
            //生成的html名称
            filename: 'home.html',
            chunks: ['home']
        }),
        new HtmlWebpackPlugin({
            template: './index.html',
            filename: 'list.html',
            chunks: ['list']
        }),
        new HtmlWebpackPlugin({
            template: './index.html',
            filename: 'detail.html',
            chunks: ['detail']
        }),
    ]
}
复制代码

  咱们以index.html做为模板文件,生成home、list、detail三个不一样的页面,而且经过chunks分别引入不一样的bundle;若是这里不写chunks,每一个页面就会引入全部生成出来的bundle。

  html-webpack-plugin还支持如下字段:

new HtmlWebpackPlugin({
    template: './index.html',
    filename: 'all.html',
    //页面注入title
    title: 'html webpack plugin title',
    //默认引入全部的chunks连接
    chunks: 'all',
    //注入页面位置
    inject: true,
    //启用hash
    hash: true,
    favicon: '',
    //插入meta标签
    meta: {
        'viewport': 'width=device-width, initial-scale=1.0'
    },
    minify: {
        //清除script标签引号
        removeAttributeQuotes: true,
        //清除html中的注释
        removeComments: true,
        //清除html中的空格、换行符
        //将html压缩成一行
        collapseWhitespace: false,
        //压缩html的行内样式成一行
        minifyCSS: true,
        //清除内容为空的元素(慎用)
        removeEmptyElements: false,
        //清除style和link标签的type属性
        removeStyleLinkTypeAttributes: false
    }
}),
复制代码

  上面设置title后须要在模板文件中设置模板字符串:

<title><%= htmlWebpackPlugin.options.title %></title>
复制代码

loader

  loader用于对模块module的源码进行转换,默认webpack只能识别commonjs代码,可是咱们在代码中会引入好比vue、ts、less等文件,webpack就处理不过来了;loader拓展了webpack处理多种文件类型的能力,将这些文件转换成浏览器可以渲染的js、css。

  module.rules容许咱们配置多个loader,可以很清晰的看出当前文件类型应用了哪些loader,loader的代码均在demo4中。

{
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {}
                }
            },
            {
                test: /\.css$/,
                use: [
                    { loader: 'style-loader' },
                    { loader: 'css-loader' }
                ]
            },
        ]
    }
}
复制代码

  咱们能够看到rules属性值是一个数组,每一个数组对象表示了不一样的匹配规则;test属性时一个正则表达式,匹配不一样的文件后缀;use表示匹配了这个文件后调用什么loader来处理,当有多个loader的时候,use就须要用到数组。

  多个loader支持链式传递,可以对资源进行流水线处理,上一个loader处理的返回值传递给下一个loader;loader处理有一个优先级,从右到左,从下到上;在上面demo中对css的处理就听从了这个优先级,css-loader先处理,处理好了再给style-loader;所以咱们写loader的时候也要注意先后顺序。

css-loader和style-loader

  css-loader和style-loader从名称看起来功能很类似,然而二者的功能有着很大的区别,可是他们常常会成对使用;安装方法:

npm i -D css-loader style-loader
复制代码

  css-loader用来解释@import和url();style-loader用来将css-loader生成的样式表经过<style>标签,插入到页面中去。

/* /src/head.css */
.head{
    display: flex;
    background: #666;
}
/* /src/foot.css */
.foot{
    background: #ccc;
}
/* /src/index.css */
@import './head.css';
@import './foot.css';
.wrap {
    background: #999;
}
复制代码

  而后在入口文件中将index.css引入,就能看到打包的效果,页面中插入了三个style标签,代码在demo4:

css-loader.png

sass-loader和less-loader

  这两个loader看名字你们也能猜到了,就是用来处理sass和less样式的。安装方法:

npm i -D sass-loader less-loader node-sass
复制代码

  在config中进行配置,代码在demo4:

{
    //其余配置
    rules: {
        test: /\.scss$/,
        use: [{
            loader: 'style-loader'
        }, {
            loader: 'css-loader'
        },{
            loader: 'sass-loader'
        }]
    },{
        test: /\.less$/,
        use: [{
            loader: 'style-loader'
        }, {
            loader: 'css-loader'
        },{
            loader: 'less-loader'
        }]
    }
}

复制代码

postcss-loader

  都0202年了,小伙伴确定不想一个一个的手动添加-moz、-ms、-webkit等浏览器私有前缀;postcss提供了不少对样式的扩展功能;啥都不说,先安装起来:

npm i -D postcss-loader
复制代码

  老规矩,仍是在config中进行配置:

rules: [{
    test: /\.scss$/,
    use: [{
        loader: 'style-loader'
    }, {
        loader: 'css-loader'
    }, {
        loader: 'postcss-loader'
    },{
        loader: 'sass-loader'
    }]
},{
    test: /\.less$/,
    use: [{
        loader: 'style-loader'
    }, {
        loader: 'css-loader'
    }, {
        loader: 'postcss-loader'
    },{
        loader: 'less-loader'
    }]
}]
复制代码

  正当咱们兴冲冲的打包看效果时,发现样式仍是老样子,并无什么改变。

why

  这是由于postcss主要功能只有两个:第一就是把css解析成JS能够操做的抽象语法树AST,第二就是调用插件来处理AST并获得结果;因此postcss通常都是经过插件来处理css,并不会直接处理,因此咱们须要先安装一些插件:

npm i -D autoprefixer postcss-plugins-px2rem cssnano
复制代码

  在项目根目录新建一个.browserslistrc文件。

> 0.25%
last 2 versions
复制代码

  咱们将postcss的配置单独提取到项目根目录下的postcss.config.js

module.exports = {
    plugins: [
        //自动添加前缀
        require('autoprefixer'),
        //px转为rem,应用于移动端
        require('postcss-plugins-px2rem')({ remUnit: 75 }),
        //优化合并css
        require('cssnano'),
    ]
}
复制代码

  有了autoprefixer插件,咱们打包后的css就自动加上了前缀。

babel-loader

  兼容低版本浏览器的痛相信不少童鞋都经历过,写完代码发现本身的js代码不能运行在IE10或者IE11上,而后尝试着引入各类polyfill;babel的出现给咱们提供了便利,将高版本的ES6甚至ES7转为ES5;咱们首先安装babel所须要的依赖:

npm i -D babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime
npm i -S @babel/runtime
复制代码

  而后在config添加loader对js进行处理:

{
    //省略其余配置
    rules: [{
        test: /\.js/,
        use: {
            loader: 'babel-loader'
        }
    }]
}
复制代码

  一样的,咱们把babel的配置提取到根目录,新建一个.babelrc文件:

{
    "presets": [
        "@babel/preset-env"
    ],
    "plugins": [
        "@babel/plugin-transform-runtime"
    ]
}
复制代码

  咱们能够在index.js中尝试写一些es6的语法,看到代码会被转译成es5,代码在demo4中。因为babel-loader的转译速度很慢,在后面咱们加入了时间插件后能够看到每一个loader的耗时,babel-loader是最耗时间;所以咱们要尽量少的使用babel来转译文件,咱们对config进行改进,

{
    //省略其余配置
    rules: [{
        test: /\.js$/,
        use: {
            loader: 'babel-loader'
        },
        // exclude: /node_modules/,
        include: [path.resolve(__dirname, 'src')]
    }]
}
复制代码

  正则上使用$来进行精确匹配,经过exclude将node_modules中的文件进行排除,include将只匹配src中的文件;能够看出来include的范围比exclude更缩小更精确,所以也是推荐使用include。

file-loader和url-loader

  file-loader和url-loader都是用来处理图片、字体图标等文件;url-loader工做时分两种状况:当文件大小小于limit参数,url-loader将文件转为base-64编码,用于减小http请求;当文件大小大于limit参数时,调用file-loader进行处理;所以咱们优先使用url-loader,首先仍是进行安装,安装url-loader以前还须要把file-loader先安装:

npm i file-loader url-loader -D
复制代码

  接下来仍是修改config:

{
    //省略其余配置
    rules: [{
        test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2)$/,
        use: {
            loader: 'url-loader',
            options: {
                //10k
                limit: 10240,
                //生成资源名称
                name: '[name].[hash:8].[ext]',
                //生成资源的路径
                outputPath: 'imgs/'
            },
            exclude: /node_modules/,
        }
    }]
}
复制代码

  咱们在css中给body添加一个小于10k的居中背景图片:

body{
    width: 100vw;
    height: 100vh;
    background: url(./bg.png) no-repeat;
    background-size: 400px 400px;
    background-position: center center;
}
复制代码

  打包后查看body的样式能够发现图片已经被替换成base64格式的url了,代码在demo4。

url-loader.png

html-withimg-loader

  若是咱们在页面上引用一个图片,会发现打包后的html仍是引用了src目录下的图片,这样明显是错误的,所以咱们还须要一个插件对html引用的图片进行处理:

npm i -D html-withimg-loader
复制代码

  老样子仍是在config中对html进行配置:

{
    //省略其余配置
    rules: [{
        test: /\.(htm|html)$/,
        use: {
            loader: 'html-withimg-loader'
        }
    }]
}
复制代码

  然鹅,打开页面发现倒是这样的:

html-withimg-loader.png

  这是由于在url-loader中把每一个图片做为一个模块来处理了,咱们还须要去url-loader中修改:

use: {
    loader: 'url-loader',
    options: {
        //10k
        limit: 10240,
        esModule: false
    }
}
复制代码

  这样咱们在页面上的图片引用也被修改了,代码在demo4中。

  html-withimg-loader会致使html-webpack-plugin插件注入title的模板字符串<%= htmlWebpackPlugin.options.title %>失效,原封不动的展现在页面上;所以,若是咱们想保留二者的功能须要在配置config中把html-withimg-loader删除而且经过下面的方式来引用图片:

<img src="<%=require('./src/bg1.png') %>" alt="" srcset="">
复制代码

vue-loader

  最后说一下一个比较特殊的vue-loader,看名字就知道是用来处理vue文件的。

npm i -D vue-loader vue-template-compiler
npm i -S vue
复制代码

  咱们首先来建立一个vue文件,具体代码在demo5中:

//src/App.vue
<template>
    <div id="app">
        <div class="box" @click="tap">{{title}}</div>
    </div>
</template>
<script>
export default {
    name: 'app',
    data(){
        return {
            title: 'app实例'
        }
    },
    methods: {
        tap(){
            this.title = this.title.split('').reverse().join('')
        }
    }
}
</script>
<style>
#app{
    font-size: 16px;
    background: #ccc;
}
</style>
复制代码

  而后在webpack的入口文件中引用它:

//src/main.js
import Vue from 'vue'
import App from './App.vue'

new Vue({
    render: h => h(App)
}).$mount('#app')
复制代码

  不过vue-loader和其余loader不太同样,除了将它和.vue文件绑定以外,还须要引入它的一个插件:

const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
    module: {
        rules: [
        //省略其余loader
        {
            test: /\.vue$/,
            loader: 'vue-loader'
        }]
    },
    plugins: [
        new VueLoaderPlugin(),
    ]
}
复制代码

  这样咱们就能愉快的在代码中写vue了。

搭建开发环境

  在上面的demo中咱们都是经过命令行打包生成dist文件,而后直接打开html或者经过static-server来查看页面的;可是开发中咱们写完代码每次都来打包会严重影响开发的效率,咱们指望的是写完代码后当即就可以看到页面的效果;webpack-dev-server就很好的提供了一个简单的web服务器,可以实时从新加载。

  首先在咱们的项目中安装依赖:

npm i -D webpack webpack-dev-server
复制代码

  webpack-dev-server的用法和wepack同样,只不过他会额外启动一个express的服务器。咱们在项目中新建一个webpack.dev.config.js配置文件,单独对开发环境进行一个配置,相关代码在demo6中:

module.exports = {
    //省略其余配置
    devServer: {
        //启动服务器端口
        port: 9000,
        //默认是localhost,只能本地访问
        host: "0.0.0.0",
        //自动打开浏览器
        open: false,
        //启用模块热替换
        hot: true,
        //启用gzip压缩
        compress: true
    },
    plugins: [
        //热更新插件
        new webpack.HotModuleReplacementPlugin({
        })
    ]
}
复制代码

  经过命令行webpack-dev-server来启动服务器,启动后咱们发现根目录并无生成任何文件,由于webpack打包到了内存中,不生成文件的缘由在于访问内存中的代码比访问文件中的代码更快。

  咱们在public/index.html的页面上有时候会引用一些本地的静态文件,直接打开页面的会发现这些静态文件的引用失效了,咱们能够修改server的工做目录,同时指定多个静态资源的目录:

contentBase: [
  path.join(__dirname, "public"),
  path.join(__dirname, "assets")
]
复制代码

  热更新(Hot Module Replacemen简称HMR)是在对代码进行修改并保存以后,webpack对代码从新打包,而且将新的模块发送到浏览器端,浏览器经过新的模块替换老的模块,这样就能在不刷新浏览器的前提下实现页面的更新。

dev-server.png

  能够看出浏览器和webpack-dev-server之间经过一个websock进行链接,初始化的时候client端保存了一个打包后的hash值;每次更新时server监听文件改动,生成一个最新的hash值再次经过websocket推送给client端,client端对比两次hash值后向服务器发起请求返回更新后的模块文件进行替换。

  咱们点击源码旁的行数看一下编译后的源码是什么样的:

source-map.png

  发现跟咱们的源码差距仍是挺大的,原本是一个简单add函数,经过webpack的模块封装,已经很难理解原来代码的含义了,所以,咱们须要将编译后的代码映射回源码;devtool中不一样的配置有不一样的效果和速度,综合性能和品质后,咱们通常在开发环境使用cheap-module-eval-source-map,在生产环境使用source-map

module.exports = {
    devtool: 'cheap-module-eval-source-map',
    //其余配置
}
复制代码

  其余各模式的对比:

devtool.png

plugins

  在上面咱们也介绍了DefinePlugin、HtmlWebpackPlugin等不少插件,咱们发现这些插件都可以不一样程度的影响着webpack的构建过程,下面还有一些经常使用的插件,plugins相关代码在demo7中。

clean-webpack-plugin

  clean-webpack-plugin用于在打包前清理上一次项目生成的bundle文件,它会根据output.path自动清理文件夹;这个插件在生产环境用的频率很是高,由于生产环境常常会经过hash生成不少bundle文件,若是不进行清理的话每次都会生成新的,致使文件夹很是庞大;这个插件安装使用很是方便:

npm i -D clean-webpack-plugin
复制代码

  安装后咱们在config中配置一下就能够了:

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
    //其余配置
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html',
        })
    ]
}
复制代码

mini-css-extract-plugin

  咱们以前的样式都是经过style-loader插入到页面中去,可是生产环境须要单独抽离样式文件,mini-css-extract-plugin就能够帮我从js中剥离样式:

npm i -D mini-css-extract-plugin
复制代码

  咱们在开发环境使用style-loader,生产环境使用mini-css-extract-plugin:

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    //其余配置
    module: {
        rules: [
            {
                test: /\.less/,
                use: [{
                    loader: isDev ? 'style-loader' : MiniCssExtractPlugin.loader
                },{
                    loader: 'css-loader'
                },{
                    loader: 'less-loader'
                }]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: "[name].[hash:8].css",
        })
    ]
}
复制代码

  引入loader后,咱们还须要配置plugin,提取的css一样支持output.filename中的占位符字符串。

optimize-css-assets-webpack-plugin

  咱们能够发现虽然配置了production模式,打包出来的js压缩了,可是打包出来的css确没有压缩;在生产环境咱们须要对css进行一下压缩:

npm i optimize-css-assets-webpack-plugin -D
复制代码

  而后也是引入插件:

const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
    //其余配置
    plugins: [
        new OptimizeCSSAssetsPlugin(),
    ]
}
复制代码

copy-webpack-plugin

  和demo6中同样,咱们在public/index.html中引入了静态资源,可是打包的时候webpack并不会帮咱们拷贝到dist目录,所以copy-webpack-plugin就能够很好地帮我作拷贝的工做了

npm i -D copy-webpack-plugin
复制代码

  在config中配置咱们须要拷贝的源路径和目标路径:

const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
    plugins: [
        new CopyWebpackPlugin({
            patterns: [
                {
                    from: 'public/js/*.js',
                    to: path.resolve(__dirname, 'dist', 'js'),
                    flatten: true,
                }
            ]
        }),
    ]
}
复制代码

ProvidePlugin

  ProvidePlugin能够很快的帮咱们加载想要引入的模块,而不用require。通常咱们加载jQuery须要先把它import:

import $ from 'jquery'
$('.box').html('box')
复制代码

  可是咱们在config中配置ProvidePlugin插件后可以不用import,直接使用$

module.exports = {
    plugins: [
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery'
        }),
    ]
}
复制代码

  可是若是在项目中引入了太多模块而且没有require会让人摸不着头脑,所以建议加载一些常见的好比jQuery、vue、lodash等。

no-idea.png

loader和plugin的区别

  介绍了这么多loader和plugin,咱们来回顾一下他们二者的区别:

loader:因为webpack只能识别js,loader至关于翻译官的角色,帮助webpack对其余类型的资源进行转译的预处理工做。 plugins:plugins扩展了webpack的功能,在webpack运行时会广播不少事件,plugin能够监听这些事件,而后经过webpack提供的API来改变输出结果。

总结

  最后,介绍了这么多,本文是webpack基础篇,还有不少生产环境的优化尚未写到;所以各位看官敬请期待优化篇。

参考:

再来一打Webpack面试题 Webpack HMR 原理解析

更多前端资料请关注公众号【前端壹读】

若是以为写得还不错,请关注个人掘金主页。更多文章请访问谢小飞的博客

相关文章
相关标签/搜索