小白笔记-grunt

Grunt 侧重对整个过程的控制管理,是任务类型的的打包工具,代码检查、预编译、合并压缩;生成雪碧图、sourceMap、版本管理;运行单元测试、监控等。

Grunt 执行异步任务

  • 执行异步任务的回调函数不能使用箭头函数
  • 异步任务完成需要调用任务内置函数告知已经完成

Grunt 标记任务失败

  • 同步任务通过 return false来标记任务失败
  • 异步任务通过内置函数 done 传递 false 参数 ***done(false)**来标记任务失败
  • 如果多任务执行,只要标记任务失败,后续任务将不在执行。添加 --force选项,会强制执行后续任务

Grunt 设置配置

  • 使用 initConfig 方法去配置参数
  • 使用 grunt.config 方法来获取参数

Grunt 插件使用

  • Grunt 插件命名规则 grunt-contrib-任务名grunt-任务名
  • Grunt 常用插件 样式预编译、JS编译、文件监听
  • 使用插件前,需要使用**loadNpmTasks**方法加载插件,然后才可以使用。load-grunt-tasks插件可自动加载所有插件

npx

npx是一个npm包执行器,有如下特点:

  • 临时安装可执行依赖包,不用全局安装,故不用担心长期的污染

  • 可以执行依赖包中的命令,安装完成自动运行

  • 自动加载 node_modules中依赖包,而无需指定$PATH,还可以指定node.js版本,命令的版本。

    1
    2
    3
    4
    5
    6
    7
    // 指定uglify-js 版本,npx 运行时,会到node_modules/.bin路径 和 环境变量$PATH里检查命令是否存。
    // 如果找不到,会临时安装,然后执行
    npx uglify-js@3.1.0 ./test/test_npx.js -o ./test/haha.js

    //  指定node版本的
    npx -p node@8 npm run build

Gulp

Gulp在4.0版本之后,无需引入gulp,然后在注册任务才可以调用,现在可直接通过exports导出任务。且取消了同步任务,约定每次任务执行完成后,都要调用回调函数的方法参数才可以。

Gulp 异步任务的多种形式

    1. 回调函数,
      标记任务失败,在方法done中传入错误对象。其后面的任务也不会在执行
    1. promise
      Promise.resolve() 任务成功; Promise.reject() 任务失败
    1. async、await
    1. 使用gulp读取文件操作时,返回文件流,gulp会自动在流上注册一个end事件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 返回文件流 
    const fs = require('fs');
    exports.stream = () => {
    const readstream = fs.createReadStream("package.json");
    const writestream = fs.createWriteStream("temp.txt");
    readstream.pipe(writestream);
    return readstream;
    }

    exports.stream2 = (done) => {
    const readstream = fs.createReadStream("package.json");
    const writestream = fs.createWriteStream("temp2.txt");
    readstream.pipe(writestream);
    readstream.on("end", () => {
    done()
    })
    }

gulp 文件操作

Gulp使用 src方法匹配到源文件,然后通过node.js的pipe将文件流 输送到 gulp插件(编译,压缩),最后通过dest方法,将编译好的文件输出到指定编译目录中。

webpack用于JavaScript应用程序的静态模块打包工具。他会在内部从一个或多个入口点构建一个依赖图(dependency graph),然后将项目中所需要的每一个模块组合成一个或多个bundles,它们均为静态资源。
对webpack而言,一起文件都是一个个模块,根据个模块之间的依赖关系,对模块进行组合和打包,最终输出浏览器能使用的静态资源。

context 基础目录

  • webpack的基础目录,必须是绝对路径,用于从配置中解析 入口点(entry)和 加载器(loader),默认为当前目录
1
2
3
4
5
6
7
8
9
10
11
12
const path = require("path");
console.log('__dirname',__dirname);
module.exports = {
mode: "development",
// 基础目录 "/home/project/webpack-config/config/src"
context: path.resolve(__dirname,"src"),
entry: "./src/index.js",
output: {
filename: "main.js",
path: path.resolve(__dirname,"../dist")
}
}

基础目录 为 /home/project/webpack-config/config/src, 运行 npx webpack --config ./config/webpack.config-1.js,会报错,找不到./src/index.js.

context,entry做如下修改,在执行 npx webpack 命令,编译正常。由此可知,context 是作为 entry 和 loader 的 根目录

1
2
3
4
5
6
7
8
9
10
11
12
const path = require("path");
console.log('__dirname',__dirname);
console.log("__filename",__filename);
module.exports = {
mode: "development",
context: path.resolve(__dirname,"../src"),
entry: "./index.js",
output: {
filename: "main.js",
path: path.resolve(__dirname, "../dist")
}
}

entry 入口文件

entry支持字符串、字符串数组、对象、函数类型

  • string 类型

    1
    entry: "./src/index.js"
  • 字符串数组 类型
    两个文件会合并打包为一个chunk文件

    1
    entry: ["./src/index.js", "./src/calc/js"]
  • 对象类型
    entry 对象有2个入口,入口 a 和 b, 通过 dependOn 配置 b 依赖于 a, 构建是会等 a构建完成后再启动b的构建。
    entry 配置有多个入口文件,所以输出output的配置也要满足,使用[name]占位符来确保每个文件具有唯一代名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const path = require("path");
module.exports = {
mode: "development",
context: path.resolve(__dirname,".."),
entry: {
b: {
dependOn: "a",
import: "./src/calc.js"
},
a: "./src/index.js"
},
output: {
filename: "[name].js",
path: path.resolve(__dirname, "../dist")
}
}

output 输出

output属性最低要求,设置为一个对象,配置输出文件的文件名,默认输出目录dist

1
2
3
output: {
filename: "bundle.js"
}

如果配置2个或多个入口文件,则应该使用 占位符来保证每个文件具有唯一的名称

生成 hash 文件名, 例如 b.ec5d198e69.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const path = require("path");
module.exports = {
mode: "development",
context: path.resolve(__dirname,".."),
entry: {
b: {
dependOn: "a",
import: "./src/calc.js"
},
a: "./src/index.js"
},
output: {
filename: "[name].[contenthash:10].js",
path: path.resolve(__dirname, "../dist")
}
}

常用的hash 占位符有:

  • hash, hash跟整个项目构建相关,每次构建生成的文件hash值都一样,只要项目中文件内容发生变化,hash值也会变化
  • contenthash, 由文件内容产生的hash值,内容不同产生的值也不一样
  • chunkhash, 每一次构建后生成的hash值都不一样,即使文件内容没有改变,不利于浏览器的缓存
生成 umd 模式的文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const path = require("path");

module.exports = {
mode: "development",
context: path.resolve(__dirname,".."),
entry: "./src/index.js",
// output: {
// library: "lgUtils",
// libraryTarget: "umd",
// filename: '[name].[hash:6].js',
// path: path.resolve(__dirname,"../dist")
// }
output: {
library: {
type: 'umd',
name: 'lqUtils'
},
filename: '[name].[hash:6].js',
path: path.resolve(__dirname,"../dist")
}
}

执行 npx webpack --config ./config/webpack.config-6.js, 生成 main.7f77f8.jsjs文件,新建index.html并引入打包后的js文件,控制台打印lqUtils,可看到输出结果。表面 打包的js文件是 umd格式,支持浏览器。 js文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/*
* ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["lqUtils"] = factory();
else
root["lqUtils"] = factory();
})(self, () => {
return /******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({

/***/ "./src/index.js":
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"a\": () => (/* binding */ a),\n/* harmony export */ \"say\": () => (/* binding */ say)\n/* harmony export */ });\nconst say = (msg) => {\n return `hello ${msg}`;\n}\nconst a = \"jiang2\";\n\n\n//# sourceURL=webpack://lqUtils/./src/index.js?");

/***/ })

/******/ });
/************************************************************************/
/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module can't be inlined because the eval devtool is used.
/******/ var __webpack_exports__ = {};
/******/ __webpack_modules__["./src/index.js"](0, __webpack_exports__, __webpack_require__);
/******/
/******/ return __webpack_exports__;
/******/ })()
;
});

tips

npm view webpack devDependencies,npm view webpack dependencies 查看webpack的依赖

脚手架工具
inquirer, 询问交互
ejs, 模板解析
在 package.json中,bin字段指向可执行文件,其值是字符串,或者 对象。

对象形式,myjs-cli 即为脚手架的名字
字符串,脚手架的名字 就是 name的值
执行脚手架,就会执行在package.json 中配置项 bin 指向的可执行文件
“bin”: {
“myjs-cli”: “bin/index.js”
},
process.cwd(),node 运行时的路径

webpack 原理
webpack 原理
自定义 loader, plugin
简易的 webpack
webpack 核心配置

entry, 可执行模块 或者 库的入口
chunk, 多个文件组成的一个代码块。可以将可执行模块和它所依赖的模块组合成一个chunk
loader, 文件转换器。 es6 转换 为 es5, scass 转换为 css
plugin, 扩展webpack功能的插件。 在 webpack 构建的生命周期节点上加入扩展hook,添加功能
webpack 构建过程?
初始化参数, 解析webpack 配置参数,合并shell传入和webpack.config.js 文件配置的参数,形成最后的配置结果
开始编译
通过上一步得到 的参数 初始化 compiler 对象
注册所有配置的插件, 插件 监听webpack构建生命周期的事件节点,做出相应的处理
执行对象的run方法开始执行编译
确认入口, 从配置的 entry 入口,开始解析文件,构建AST语法树,找出依赖,递归下去
编译模块, 递归中 根据文件类型和loader配置,调用所有配置的loader对文件进行转换,在找出该模块依赖的模块,在递归本步骤,直到所有入口依赖的文件都经过处理
完成模块编译并输出,递归完成后,等到每个文件结果,包含每个模块以及它们之间的依赖关系,根据 entry 配置生成代码块 chunk
输出完成, 输出所有的chunk 到文件系统
自定义 plugins
plugins 的钩子函数分为 compiler钩子,和 compilation钩子,每个钩子还有很多各自的钩子周期函数,在不同钩子周期函数中,webpack 进行不同的操作。

如何创建 webpack plugin?
构建函数
扩展 apply 方法
指定webpack 自身的事件钩子
处理 webpack 内部实例的特定数据
功能完成后,调用 webpack 提供的回调函数