webpack入门篇

// package.json
{
	"name": "webpack-demo",
	"version": "1.0.0",
	"description": "webpack学习项目",
	"main": "index.js",
	"scripts": {
		"dev": "webpack --mode development  --module-bind js=babel-loader",
		"build": "webpack --mode production --output ./output/main.js"
	},
	"author": "chen",
	"license": "ISC",
	"devDependencies": {
		"@babel/core": "^7.10.2",
		"@babel/preset-env": "^7.10.2",
		"babel-loader": "^8.1.0",
		"webpack": "^4.43.0",
		"webpack-cli": "^3.3.11"
	}
}

webpack.config.js 配置文件

// webpack.config.js文件

// path 模块提供用于处理文件路径和目录路径的实用工具。 它可以使用以下方式访问:
const path = require('path')

// 然后通过module.exports将 Webpack 的配置导出
module.exports = {
	mode: 'development',
	entry: './src/index.js',
	output: {
		// path.resolve( )方法  可以将路径或者路径片段解析成绝对路径
		// 只传入__dirname也可以自动调用path.resolve方法 但是无法和后面的路径进行拼接
		// __dirname代表的是当前文件的绝对路径
		path: path.resolve(__dirname, 'dist'),
		filename: 'foo.bundle.js',
	},
}


// 😀如果我们只使用一个配置文件来区分生产环境(production)和开发环境(development)
// 则可以使用函数类型的 Webpack 配置,函数类型的配置必须返回一个配置对象,如下面:

// Webpack 配置函数接受两个参数env和argv:分别对应着 环境对象 和 Webpack-CLI 的命令行选项,例如上面代码中的--optimize-minimize。

module.exports = (env, argv) => {
	return {
		mode: env.production ? 'production' : 'development',
		devtool: env.production ? 'source-maps' : 'eval',
		plugins: [
			new TerserPlugin({
				terserOptions: {
					compress: argv['optimize-minimize'], // 只有传入 -p 或 --optimize-minimize
				},
			}),
		],
	}
}

配置的使用

默认情况下,Webpack 会查找执行目录下面的webpack.config.js作为配置,如果需要指定某个配置文件,可以使用下面的命令:

--config build/webpack.dev.js

Webpack 常见名词解释

参数 说明
entry 项目入口
module 开发中每一个文件都可以看做 module,模块不局限于 js,也包含 css、图片等
chunk 代码块,一个 chunk 可以由多个模块组成
loader 模块转化器,模块的处理器,对模块进行转换处理
plugin 扩展插件,插件可以处理 chunk,也可以对最后的打包结果进行处理,可以完成 loader 完不成的任务
bundle 最终打包完成的文件,一般就是和 chunk 一一对应的关系,bundle 就是对 chunk 进行编译压缩打包等处理后的产出

mode 模式

Webpack4.0 开始引入了mode配置,通过配置mode=development或者mode=production来制定是开发环境打包,还是生产环境打包,比如生产环境代码需要压缩,图片需要优化,Webpack 默认mode是生产环境,即mode=production

在配置文件中设置mode

module.exports = {
    mode: 'development'
};

Webpack 的入口(entry)和输出(output)

webpack 是一个模块打包工具,能够从一个需要处理的 JavaScript 文件开始,构建一个依赖关系图(dependency graph),该图映射到了项目中每个模块,然后将这个依赖关系图输出到一个或者多个 bundle 中

Webpack 的两个核心概念:entryoutput,即入口和输出

Webpack 是从指定的入口文件(entry)开始,经过加工处理,最终按照output设定输出固定内容的 bundle;而这个加工处理的过程,就用到了loaderplugin两个工具;loader是源代码的处理器,plugin解决的是 loader处理不了的事情。

在介绍entry之前,介绍下context(上下文),context即项目打包的相对路径上下文,如果指定了context="/User/test/webpack",那么我们设置的entryoutput的相对路径都是相对于/User/test/webpack的,包括在 JavaScript 中引入模块也是从这个路径开始的。由于context的作用,决定了context值必须是一个绝对路径

// webpack.config.js
module.exports = {
    context: '/Users/test/webpack'
};

Tips:在实际开发中 context 一般不需要配置,不配置则默认为process.cwd()即工作目录。

entry入口

Webpack 的entry支持多种类型,包括字符串、对象、数组。从作用上来说,包括了单文件入口多文件入口两种方式。

单文件入口

单文件的用法如下:

module.exports = {
    entry: 'path/to/my/entry/file.js'
};
// 或者使用对象方式
module.exports = {
    entry: {
        main: 'path/to/my/entry/file.js'
    }
};

单文件入口可以快速创建一个只有单一文件入口的情况,例如 library 的封装,但是单文件入口的方式相对来说比较简单,在扩展配置的时候灵活性较低。

module.exports = {
    mode: 'development',
    entry: ['./src/app.js', './src/home.js'],
    output: {
        filename: 'array.js'
    }
};

Tips:上面配置无论是字符串还是字符串数组的 entry,实际上都是只有一个入口,但是在打包产出上会有差异:

多文件入口

多文件入口是使用对象语法来通过支持多个entry,多文件入口的对象语法相对于单文件入口,具有较高的灵活性,例如多页应用、页面模块分离优化。多文件入口的语法如下:

module.exports = {
    entry: {
        home: 'path/to/my/entry/home.js',
        search: 'path/to/my/entry/search.js',
        list: 'path/to/my/entry/list.js'
    }
};
👉上面的语法将entry分成了 3 个独立的入口文件,这样会打包出来三个对应的 bundle,

Tips:对于一个 HTML 页面,我们推荐只有一个 entry ,通过统一的入口,解析出来的依赖关系更方便管理和维护。

output 输出

webpack 的output是指定了entry对应文件编译打包后的输出 bundle。output的常用属性是:

  • path:此选项制定了输出的 bundle 存放的路径,比如distoutput
  • filename:这个是 bundle 的名称
  • publicPath:指定了一个在浏览器中被引用的 URL 地址,后面详细介绍

当不指定 output 的时候,默认输出到 dist/main.js ,即 output.pathdistoutput.filenamemain

一个 webpack 的配置,可以包含多个entry,但是只能有一个output。对于不同的entry可以通过output.filename占位符语法来区分,比如:

module.exports = {
    entry: {
        home: 'path/to/my/entry/home.js',
        search: 'path/to/my/entry/search.js',
        list: 'path/to/my/entry/list.js'
    },
    output: {
        filename: '[name].js',
        path: __dirname + '/dist'
    }
};
NeqK1I.png

其中[name]就是占位符,它对应的是entrykeyhomesearchlist),所以最终输出结果是:

path/to/my/entry/home.js → dist/home.js
path/to/my/entry/search.js → dist/search.js
path/to/my/entry/list.js → dist/list.js

我将 Webpack 目前支持的占位符列出来:

占位符 含义
[hash] 模块标识符的 hash
[chunkhash] chunk 内容的 hash
[name] 模块名称
[id] 模块标识符
[query] 模块的 query,例如,文件名 ? 后面的字符串
[function] 一个 return 出一个 string 作为 filename 的函数

[hash][chunkhash] 的长度可以使用 [hash:16](默认为 20)来指定。或者,通过指定output.hashDigestLength在全局配置长度,那么他们之间有什么区别吗?

[hash]:是整个项目的 hash 值,其根据每次编译内容计算得到,每次编译之后都会生成新的 hash,即修改任何文件都会导致所有文件的 hash 发生改变;在一个项目中虽然入口不同,但是 hash 是相同的;hash 无法实现前端静态资源在浏览器上长缓存,这时候应该使用 chunkhash;

[chunkhash]:根据不同的入口文件(entry)进行依赖文件解析,构建对应的 chunk,生成相应的 hash;只要组成 entry 的模块文件没有变化,则对应的 hash 也是不变的,所以一般项目优化时,会将公共库代码拆分到一起,因为公共库代码变动较少的,使用 chunkhash 可以发挥最长缓存的作用;

[hash]、[chunkhash]和[contenthash]都支持[xxx:length]的语法。

Tips: 占位符是可以组合使用的,例如[name]-[hash:8]

output.publicPath

对于使用<script><link>标签时,当文件路径不同于他们的本地磁盘路径(由output.path指定)时,output.publicPath被用来作为src或者link指向该文件。这种做法在需要将静态文件放在不同的域名或者 CDN 上面的时候是很有用的。

module.exports = {
    output: {
        path: '/home/git/public/assets',
        publicPath: '/assets/'
    }
};

则输出:

<head>
    <link href="/assets/logo.png" />
</head>

上面的/assets/logo.png就是根据publicPath输出的,output.path制定了输出到本地磁盘的路径,而output.publicPath则作为实际上线到服务器之后的 url 地址。所以我们在上 CDN 的时候可以这样配置:

module.exports = {
    output: {
        path: '/home/git/public/assets',
        publicPath: 'http://cdn.example.com/assets/'
    }
};

则输出:

<head>
    <link href="http://cdn.example.com/assets/logo.png" />
</head>

output.library

如果我们打包的目的是生成一个供别人使用的库,那么可以使用output.library来指定库的名称,库的名称支持占位符和普通字符串:

module.exports = {
    output: {
        library: 'myLib' // '[name]'
    }
};

resolve

Webpack 进行构建的时候会从入口文件开始(entry)遍历寻找各个模块的依赖,resolve 配置是帮助 Webpack 查找依赖模块的

通过 resolve 的配置,可以帮助 Webpack 快速查找依赖,也可以替换对应的依赖(比如开发环境用 dev 版本的 lib 等)。resolve 的基本配置语法如下:

module.exports = {
    resolve: {
        // resolve的配置
    }
};

下面来介绍下常用的 resolve 配置。

resolve.extensions

resolve.extensions是帮助 Webpack 解析扩展名的配置,默认值:['.wasm', '.mjs', '.js', '.json'],所以我们引入 js 和 json 文件,可以不写它们的扩展名,通常我们可以加上 .css.less等,但是要确保同一个目录下面没有重名的 css 或者 js 文件,如果存在的话,还是写全路径吧。

module.exports = {
    resolve: {
        extensions: ['.js', '.json', '.css']
    }
};

resolve.alias

resolve.alias 是最常用的配置,通过设置 alias 可以帮助 webpack 更快查找模块依赖,而且也能使我们编写代码更加方便。例如,我们在实际开发中经常会把源码都放到src文件夹,目录结构如下:

src
├── lib
│   └── utils.js
└── pages
    └── demo
        └── index.js

🎇这时可以通过设置 alias 来缩短这种写法,例如:
module.exports = {
    resolve: {
        alias: {
            src: path.resolve(__dirname, 'src'),
            '@lib': path.resolve(__dirname, 'src/lib')
        }
    }
};

🎉经过设置了 alias,我们可以在任意文件中,不用理会目录结构,直接使用require('@lib/utils')或者require('src/lib/utils')来帮助 Webpack 定位模块。

module

在 webpack 解析模块的同时,不同的模块需要使用不同类型的模块处理器来处理,这部分的设置就在module配置中。module 有两个配置:module.noParsemodule.rules

module.noParse

module.noParse配置项可以让Webpack忽略对部分没采用模块化的文件的递归解析和处理,这样做的好处是能提高构建性能,接收的类型为正则表达式、正则表达式数组或者接收模块路径参数的一个函数:

module.exports = {
    module: {
        // 使用正则表达式
        noParse: /jquery|lodash/

        // 使用函数,从 Webpack 3.0.0 开始支持
        noParse: (content) => {
            // content 代表一个模块的文件路径
            // 返回 true or false
            return /jquery|lodash/.test(content);
        }
    }
}

Tips:这里一定要确定被排除出去的模块代码中不能包含importrequiredefine等内容,以保证 webpack 的打包包含了所有的模块,不然会导致打包出来的 js 因为缺少模块而报错。

module.rules

module.rules是在处理模块时,将符合规则条件的模块,提交给对应的处理器来处理,通常用来配置 loader,其类型是一个数组,数组里每一项都描述了如何去处理部分文件。每一项 rule 大致可以由以下三部分组成:

  1. 条件匹配:通过testincludeexclude等配置来命中可以应用规则的模块文件;

    🎈 Tips :exclude 的优先级高于 test 和 include

  2. 应用规则:对匹配条件通过后的模块,使用use配置项来应用loader,可以应用一个 loader 或者按照从后往前的顺序应用一组 loader,当然我们还可以分别给对应 loader 传入不同参数;

  3. 重置顺序:一组 loader 的执行顺序默认是从后到前(或者从右到左)执行,通过enforce选项可以让其中一个 loader 的执行顺序放到最前(pre)或者是最后(post)。

条件匹配

如上所述,条件匹配相关的配置有testincludeexcluderesourceresourceQueryissuer。条件匹配的对象包括三类:resourceresourceQueryissuer

  • resource:请求文件的绝对路径。它已经根据 resolve 规则解析;
  • issuer: 被请求资源(requested the resource)的模块文件的绝对路径,即导入时的位置。

举例来说明:从 app.js 导入 './style.css?inline'

  • resource/path/to/style.css
  • resourceQuery?之后的inline
  • issuer/path/to/app.js

来看下 rule 对应的配置与匹配的对象关系表:

rule 配置项 匹配的对象
test resource类型
include resource类型
exclude resource类型
resource resource类型
resourceQuery resourceQuery类型
issuer issuer类型

举例说明,下面rule 的配置项,匹配的条件为:

匹配的条件为:来自srctest文件夹,不包含node_modulesbower_modules子目录,模块的文件路径为.tsx.jsx结尾的文件。

{
    test: [/\.jsx?$/, /\.tsx?$/],
    include: [
        path.resolve(__dirname, 'src'),
        path.resolve(__dirname, 'test')
    ],
    exclude: [
        path.resolve(__dirname, 'node_modules'),
        path.resolve(__dirname, 'bower_modules')
    ]
}

Loader 配置

loader是解析处理器,通过loader我们可以将 ES6 语法的 js 转化成 ES5 的语法,可以将图片转成 base64 的dataURL,在 JavaScript 文件中直接import css 和 html 也是通过对应的loader来实现的。

我们在使用对应的loader之前,需要先安装它,例如,我们要在 JavaScript 中引入 less,则需要安装less-loader

npm i -D less-loader

然后在module.rules中指定*.less文件都是用less-loader

module.exports = {
    module:{
        rules:[
            test: /\.less$/,
            use:'less-loader'
        ]
    }
}

简单来理解上面的配置,test项使用 /\.less$/正则匹配需要处理的模块文件(即 less 后缀的文件),然后交给less-loader来处理,这里的less-loader是个 string,最终会被作为require()的参数来直接使用。

这样 less 文件都会被less-loader处理成对应的 css 文件。

除了直接在webpack.config.js使用 loader 的方式之外,还可以在对应的 JavaScript 文件中使用 loader:

Loader 的参数

给 loader 传参的方式有两种:

  1. 通过options传入
  2. 通过query的方式传入:
// config内写法,通过 options 传入
module: {
    rules: [{
        test: /\.html$/,
        use: [{
            loader: 'html-loader',
            options: {
                minimize: true,
                removeComments: false,
                collapseWhitespace: false
            }
        }]
    }]
}
// config内写法,通过 query 传入
module: {
    rules: [{
      test: /\.html$/,
      use: [ {
        loader: 'html-loader?minimize=true&removeComments=false&collapseWhitespace=false',
      }]
    }]
}

Loader 的解析顺序

对于一些类型的模块,简单配置一个 loader 是不能够满足需求的,例如 less 模块类型的文件,只配置了 less-loader 仅仅是将 Less 语法转换成了 CSS 语法,但是 JS 还是不能直接使用,所以还需要添加css-loader来处理,这时候就需要注意 Loader 的解析顺序了。前面已经提到了,Webpack 的 Loader 解析顺序是从右到左(从后到前)的,即:

// 数组写法,从后到前
module.exports = {
    module: {
        rules: [
            {
                test: /\.less$/,
                use: [
                    {
                        loader: 'style-loader'
                    },
                    {
                        loader: 'css-loader'
                    },
                    {
                        loader: 'less-loader'
                    }
                ]
            }
        ]
    }
};

如果需要调整 Loader 的执行顺序,可以使用enforce,enforce取值是pre|post,pre表示把放到最前,post是放到最后:

use: [
    {
        loader: 'babel-loader',
        options: {
            cacheDirectory: true
        },
        // enforce:'post' 的含义是把该 loader 的执行顺序放到最后
        // enforce 的值还可以是 pre,代表把 loader 的执行顺序放到最前
        enforce: 'post'
    }
];

plugin 插件

plugin是Webpack 的重要组成部分,通过plugin可以解决loader解决不了的问题。

例如有Gzip压缩

Webpack 本身就是有很多插件组成的,所以内置了很多插件,我们可以直接通过webpack对象的属性来直接使用,例如:webpack.optimize.UglifyJsPlugin

module.exports = {
    //....
    plugins: [
        // 压缩js
        new webpack.optimize.UglifyJsPlugin();
    ]
}

除了内置的插件,我们也可以通过 NPM 包的方式来使用插件:

// 非默认的插件
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
    //....
    plugins: [
        // 导出css文件到单独的内容
        new ExtractTextPlugin({
            filename: 'style.css'
        })
    ]
};

Tips:loader面向的是解决某个或者某类模块的问题,而plugin面向的是项目整体,解决的是loader解决不了的问题。

小结

在本小节中,我们讲解了Webpack 相关的除entryoutput外的基础配置项,这里总结下项目经常配置的并且比较重要的配置项列表,供大家复习本小节内容:

resolve:模块依赖查找相关的配置

  • resolve.extensions:可以省略解析扩展名的配置,配置太多反而会导致webpack 解析效率下降;
  • resolve.alias:通过设置 alias 可以帮助 webpack 更快查找模块依赖,精简代码书写时相对路径的书写;

module.rules:loader 相关的配置,每个 rule 重要的内容有:

  • test:正则匹配需要处理的模块文件;
  • use:loader 数组配置,内部有loaderoptions
  • include:包含;
  • exclude:排除;

plugins:插件。

posted @ 2020-06-18 16:09  仲吕  阅读(278)  评论(0编辑  收藏  举报