webpack (1)——核心概念的理解

4,943 阅读8分钟

引子

为什么要写这篇文章?因为今天掘金的早报有一篇关于 Webpack 的入门的文章,读完之后发现自己确实该学习一下 Webpack 了,所以有了这篇文章。
我对于 webpack 的了解仅限于在 vue-cli 中接触过,当然都是现成的,自己也从没操作过。
但是随着自己的成长,这些必须要了解一下,接下来简单都构建一个属于自己的 webpack 打包工具...

  1. webpack (1)——核心概念的理解
  2. webpack(2)——配置项详解
  3. ...

初试 webpack

我在初次学习新东西的时候,都会先先弄懂三个问题:

  1. 它是什么东西;
  2. 它能干什么;
  3. 它每一步大体是什么意思。

概念

webpack 文档中是这样定义的:

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

image

配合其官网的首页图不难理解其含义:一切文件如:JavaScriptCSSSASSIMG/PNG等,在 Webpack 眼中都是一个个模块,通过对 Webpack 进行配置,对模块进行组合和打包。经过 Webpack 的处理,最终会输出浏览器能使用的静态资源。

核心概念

基本理解了什么是 Webpack 、能干什么之后,接下来就开始理一下如何使用它。 顺着 Webpack 的文档学习,在正式使用之前,它需要我们理解几个核心概念:

  • Entry 入口
  • Output 输出结果
  • Loader 模块转换器
  • Module 模块
  • Chunk 代码块
  • Plugin 插件

Entry

entry 是配置模块的入口,它指示 Webpack 执行构建的第一步。
可以在配置文件中配置 entry 属性,来指定一个或多个入口点

entry 类型有三种:字符串、数组、对象。

  • String"./src/entry" 入口模块的文件路径,可以是相对路径
  • array : ["./src/entry1", "./src/entry2"] 入口模块的文件路径,可以是相对路径。与字符串类型不同的是数组可将多个文件打包为一个文件
  • object{ a: './src/entry-a', b: ['./src/entry-b1', './app/entry-b2']} 配置多个入口,每个入口有一个 Chunk

Output

output 配置如何输出最终想要的代码。output 是一个 object,里面包含一系列配置项。

  • filename 用于输出文件的文件名。
  • path 目标输出目录的绝对路径,必须是绝对路径。

module、Loader

module 配置如何处理模块

配置Loader

Loader 可以看作具有文件转换功能的翻译员,配置里的 module.rules 数组配置了一组规则,告诉 Webpack 在遇到哪些文件时使用哪些 Loader 去加载和转换。

  • use 属性的值需要是一个由 Loader 名称组成的数组,Loader 的执行顺序是由后到前的;
  • 每一个 Loader 都可以通过 URL querystring 的方式传入参数,例如 css-loader?minimize 中的 minimize 告诉 css-loader 要开启 CSS 压缩。

Plugins

Plugin 用于扩展 Webpack 功能,各种各样的 Plugin 几乎让 Webpack 可以做任何构建相关的事情。

配置 Plugin

Plugin 的配置很简单,plugins 配置项接受一个数组,数组里每一项都是一个要使用的 Plugin 的实例,Plugin 需要的参数通过构造函数传入。

当然使用 Plugin 的难点在于掌握 Plugin 本身提供的配置项,而不是如何在 Webpack 中接入 Plugin

想实现某项 Webpack 没有的功能去 gayHub 碰碰运气吧!哦不,是 github 碰碰运气吧!

Chunk

Chunk,代码块,即打包后输出的文件。

Webpack 会为每个 Chunk 取一个名称,可以根据 Chunk 的名称来区分输出的文件名。

filename: '[name].js'

一个入口文件,默认 chunknamemain。 除了内置变量 name,与 chunk 相关的变量还有:

  • id Chunk 的唯一标识,从0开始
  • name Chunk 的名称
  • hash Chunk 的唯一标识的 Hash
  • chunkhash Chunk 内容的 Hash

简单配置一个 webpack

了解了基本概念就直接开车吧,咳咳咳,开始实战。

// ./webpack.config.js

module.exports = {
    mode: 'development',
    entry:{
        app1: './src/index.js',
        app2: './src/main.js'
    },
    output: {
        path: __dirname + '/dist',
        filename: '[hash][name].js'
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                loader: 'style-loader!css-loader'
            },
            {
                test: /\.js$/,
                use: 'babel-loader',
                exclude: /node_modules/
            }
        ]
    }
};
// ./src/index.css
html {
    background: #333;
    color: #fff;
}
// ./src/index.js
import './index.css'
const sleep = async time => await new Promise( resolve => {
    setTimeout( () => {
       console.log(`等待${time}s`)
        return resolve
    }, time*1000)
} )
sleep(5)
console.log(`Success!`)

// ./src/main.js
const sleep = async time => await new Promise( resolve => {
    setTimeout( () => {
        console.log(`等待${time}s`)
        return resolve
    }, time*1000)
} )
sleep(2)


// .babelrc
{
  "presets": [
    "es2015",
    "stage-0"
  ],
  "plugins": [
    ["transform-runtime", {
      "polyfill": false,
      "regenerator": true
    }]
  ]
}

// package.json
{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "webpack.config.js",
  "dependencies": {
    "babel-core": "^6.26.3",
    "babel-polyfill": "^6.26.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-stage-0": "^6.24.1",
    "css-loader": "^0.28.11",
    "style-loader": "^0.21.0",
    "webpack": "^4.8.3",
    "webpack-cli": "^2.1.3"
  },
  "devDependencies": {
    "babel-loader": "^7.1.4",
    "babel-plugin-transform-runtime": "^6.23.0"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Aditya",
  "license": "ISC"
}

上述代码是我写的第一个 webpack demo,入门是真的简单,所以很快的根据文档配置了一个能够打包 css、es6、async/awaitWebpack

其中一些依赖项的安装如下

// npm i -D 是 npm install --save-dev 的简写
npm i -D webpack webapck-cli

npm i -D css-loader style-loader

npm i -D babel-loader babel-core babel-polyfill babel-preset-es2015 babel-preset-stage-0 babel-plugin-transform-runtime

css-loader和style-loader

在配置过程中遇到的问题:css代码被打包了,却没有生效。

一开始在配置 Loader 中,我只写了 css-loader,因为今天看的博文中的栗子就只写 css-loader , 殊不知人家根本就没写任何 CSS 样式=。=

所以善用了一下手中的搜索引擎,从而得知这个过程应该是这样的:

Webpack 在遇到以 .css 结尾的文件时先使用 css-loader 读取 CSS 文件,再交给 style-loaderCSS 内容注入到 JavaScript 里。

style-loader 的工作原理大概是这样的:把 CSS 内容用 JavaScript 里的字符串存储起来, 在网页执行 JavaScript 时通过 DOM 操作动态地往 head 标签里插入 style 标签。

再来说说 babel

了解 ES 6 的同学都应该知道,目前部分浏览器和 Node.js 已经支持 ES6,但由于它们对 ES6 所有的标准支持不全,这导致在开发中不敢全面地使用 ES6。 通常我们需要把采用 ES6 编写的代码转换成目前已经支持良好的 ES5 代码,这包含两件事:

  1. 把新的 ES6 语法用 ES5 实现,例如 ES6 的 class 语法用 ES5 的 prototype 实现。
  2. 给新的 API 注入 polyfill ,例如使用新的 fetch API 时注入对应的 polyfill 后才能让低端浏览器正常运行。

Babel 可以方便的完成以上2件事。 Babel 是一个 JavaScript 编译器,能将 ES6 代码转为 ES5 代码,让你使用最新的语言特性而不用担心兼容性问题,并且可以通过插件机制根据需求灵活的扩展。

Babel 执行编译的过程中,会从项目根目录下的 .babelrc 文件读取配置。.babelrc 是一个 JSON 格式的文件,内容大致如下:

{
  "presets": [
    "es2015",
    "stage-0"
  ],
  "plugins": [
    ["transform-runtime", {
      "polyfill": false,
      "regenerator": true
    }]
  ]
}

babel-loader

babel-loader 是必须的,在 Webpack 通过 Loader 接入 Babel,完成转码

babel-preset-es2015和babel-preset-stage-0

presets 属性告诉 Babel 要转换的源码使用了哪些新的语法特性,一个 Presets 对一组新语法特性提供支持,多个 Presets 可以叠加。 Presets 其实是一组 Plugins 的集合,每一个 Plugin 完成一个新语法的转换工作。

  1. 已经被写入 ECMAScript 标准里的特性,由于之前每年都有新特性被加入到标准里,所以又可细分为:
  • es2015 包含在2015里加入的新特性;
  • es2016 包含在2016里加入的新特性;
  • es2017 包含在2017里加入的新特性;
  • es2017 包含在2017里加入的新特性;
  • env 包含当前所有 ECMAScript 标准里的最新特性。
  1. 被社区提出来的但还未被写入 ECMAScript 标准里特性,这其中又分为以下四种:
  • stage0 只是一个美好激进的想法,有 Babel 插件实现了对这些特性的支持,但是不确定是否会被定为标准;
  • stage1 值得被纳入标准的特性;
  • stage2 该特性规范已经被起草,将会被纳入标准里;
  • stage3 该特性规范已经定稿,各大浏览器厂商和 Node.js 社区开始着手实现;
  • stage4 在接下来的一年将会加入到标准里去。

3.为了支持一些特定应用场景下的语法,和 ECMAScript 标准没有关系,例如 babel-preset-react 是为了支持 React 开发中的 JSX 语法。

babel-plugin-transform-runtime

Plugin 属性告诉 Bable 要使用那些插件,插件可以控制如何转换代码。

官方给出的定义是: babel-plugin-transform-runtimeBabel 官方提供的一个插件,作用是减少冗余代码。 但是我们为什么要使用它?主要是因为使用 ES 7async 函数。

因为 runtime 编译器插件做了以下三件事:

  • 当你使用 generators/async 函数时,自动引入 babel-runtime/regenerator
  • 自动引入 babel-runtime/core-js 并映射 ES 6 静态方法和内置插件。
  • 移除内联的 Babel helper 并使用模块 babel-runtime/helpers 代替。

该插件的具体信息:transform-runtime

至于 babel 的其他的具体用法,可以查阅 Babel官方文档

写在最后

对于 Webpack 第一步学习就到这里结束了,翻阅的资料有点多,需要学习的东西太多了,一时半会也记不住,算是铺个路吧,希望今后的学习能够顺畅一些。
欢迎大佬指点不足之处。