Skip to content

Vue 脚手架启动

image-20240609212348260

Vue 脚手架加载配置

如果只想看 vue 脚手架有什么 webpack 配置,可以执行

json
vue inspect --mode=development > dev.config.js
vue inspect --mode=production > prod.config.js

什么是 Gulp?

什么是 Gulp? A toolkit to automate & enhance your workflow;

一个工具包,可以帮你自动化和增加你的工作流;

image-20240609215407073

Gulp 和 Webpack

gulp 的核心理念是task runner

可以定义自己的一系列任务,等待任务被执行;

基于文件 Stream 的构建流;

我们可以使用 gulp 的插件体系来完成某些任务;

webpack 的核心理念是module bundler

webpack 是一个模块化的打包工具;

可以使用各种各样的 loader 来加载不同的模块;

可以使用各种各样的插件在 webpack 打包的生命周期完成其他的任务;

gulp 相对于 webpack 的优缺点:

gulp 相对于 webpack 思想更加的简单、易用,更适合编写一些自动化的任务;

但是目前对于大型项目(Vue、React、Angular)并不会使用 gulp 来构建,比如默认 gulp 是不支持模块化的;

Gulp 的基本使用

首先,我们需要安装 gulp:

json
# 全局安装
npm install gulp -g
# 局部安装
npm install gulp

其次,编写 gulpfile.js 文件,在其中创建一个任务:

javascript
// 编写简单的任务
const foo = async (cb) => {
  console.log("第一个gulp任务");
  cb();
};

// 导出的任务
module.exports = {
  foo,
};

最后,执行 gulp 命令:

json
npx gulp foo

创建 gulp 任务

每个 gulp 任务都是一个异步的 JavaScript 函数:

此函数可以接受一个 callback 作为参数,调用 callback 函数那么任务会结束;

或者是一个返回 stream、promise、event emitter、child process 或 observable 类型的函数;

任务可以是 public 或者 private 类型的:

公开任务(Public tasks) 从 gulpfile 中被导出(export),可以通过 gulp 命令直接调用;

私有任务(Private tasks) 被设计为在内部使用,通常作为 series() 或 parallel() 组合的组成部分;

异步任务

javascript
// 编写异步的gulp任务
// async的时候bar函数会返回一个Promise
const bar = (cb) => {
  setTimeout(() => {
    console.log("bar任务被执行~");
    cb();
  }, 2000);
};

module.exports = {
  bar,
};

补充:gulp4 之前, 注册任务时通过 gulp.task 的方式进行注册的

javascript
const gulp = require("gulp");

// 早期编写任务的方式(gulp4.x之前)
gulp.task("foo2", (cb) => {
  console.log("第二个gulp任务");
  cb();
});

默认任务

我们可以编写一个默认任务:

javascript
// 默认任务
module.exports.default = (cb) => {
  console.log("default task exec~");
  cb();
};

执行 gulp 命令:

json
npx gulp

任务组合 series 和 parallel

通常一个函数中能完成的任务是有限的(放到一个函数中也不方便代码的维护),所以我们会将任务进行组合。

gulp 提供了两个强大的组合方法:

series():串行任务组合;

parallel():并行任务组合;

javascript
const { series, parallel } = require("gulp");

const foo1 = (cb) => {
  setTimeout(() => {
    console.log("foo1 task exec~");
    cb();
  }, 2000);
};

const foo2 = (cb) => {
  setTimeout(() => {
    console.log("foo2 task exec~");
    cb();
  }, 1000);
};

const foo3 = (cb) => {
  setTimeout(() => {
    console.log("foo3 task exec~");
    cb();
  }, 3000);
};

const seriesFoo = series(foo1, foo2, foo3);
const parallelFoo = parallel(foo1, foo2, foo3);

module.exports = {
  seriesFoo,
  parallelFoo,
};

执行 npx gulp seriesFoo,执行顺序是,foo1 foo2 foo3,按顺序执行,后面一个的执行要等前面的完成才能执行。

执行 npx gulp parallelFoo,执行顺序是,foo2 foo1 foo3,并行执行,哪个先执行完就先打印哪个。

读取和写入文件

gulp 暴露了 src() 和 dest() 方法用于处理计算机上存放的文件。

src() 接受参数,并从文件系统中读取文件然后生成一个 Node 流(Stream),它将所有匹配的文件读取到内存中并通过流

(Stream)进行处理;

由 src() 产生的流(stream)应当从任务(task 函数)中返回并发出异步完成的信号;

dest() 接受一个输出目录作为参数,并且它还会产生一个 Node 流(stream),通过该流将内容输出到文件中;

流(stream)所提供的主要的 API 是 .pipe() 方法,pipe 方法的原理是什么呢?

pipe 方法接受一个 转换流(Transform streams)或可写流(Writable streams);

那么转换流或者可写流,拿到数据之后可以对数据进行处理,再次传递给下一个转换流或者可写流;

glob 文件匹配

src() 方法接受一个 glob 字符串或由多个 glob 字符串组成的数组作为参数,用于确定哪些文件需要被操作。

glob 或 glob 数组必须至少匹配到一个匹配项,否则 src() 将报错;

glob 的匹配规则如下:

(一个星号*):在一个字符串中,匹配任意数量的字符,包括零个匹配;

json
"*.js"

(两个星号**):在多个字符串匹配中匹配任意数量的字符串,通常用在匹配目录下的文件;

json
"scripts/**/*.js"

(取反!):

由于 glob 匹配时是按照每个 glob 在数组中的位置依次进行匹配操作的;

所以 glob 数组中的取反(negative)glob 必须跟在一个非取反(non-negative)的 glob 后面;

第一个 glob 匹配到一组匹配项,然后后面的取反 glob 删除这些匹配项中的一部分;

json
["scripts/**/*.js", "!scripts/vendor/"]

读取 src 目录下所有以.js 结尾的文件,然后拷贝一份到 dist 目录下

javascript
const { src, dest } = require("gulp");

const copyFile = () => {
  // 1.读取文件 2.写入文件
  return src("./src/**/*.js").pipe(dest("./dist"));
};

module.exports = {
  copyFile,
};

对文件进行转换

如果在这个过程中,我们希望对文件进行某些处理,可以使用社区给我们提供的插件。

比如我们希望 ES6 转换成 ES5,那么可以使用 babel 插件;

如果我们希望对代码进行压缩和丑化,那么可以使用 uglify 或者 terser 插件;

babel 可以单独创建一个 babel.config.js 进行配置

javascript
module.exports = {
  presets: ["@babel/preset-env"],
};
javascript
const { src, dest } = require("gulp");

const babel = require("gulp-babel");
const terser = require("gulp-terser");

const jsTask = () => {
  return src("./src/**/*.js")
    .pipe(babel())
    .pipe(terser({ mangle: { toplevel: true } }))
    .pipe(dest("./dist"));
};

module.exports = {
  jsTask,
};

Gulp 的文件监听

gulp api 中的 watch() 方法利用文件系统的监控程序(file system watcher)将 与进行关联。

使用 watch 之后,只有文件有改变,就会自动执行 npx gulp jsTask。

javascript
const { src, dest, watch } = require("gulp");

const babel = require("gulp-babel");
const terser = require("gulp-terser");

const jsTask = () => {
  return src("./src/**/*.js")
    .pipe(babel())
    .pipe(terser({ mangle: { toplevel: true } }))
    .pipe(dest("./dist"));
};

// watch函数监听内容的改变
watch("./src/**/*.js", jsTask);

module.exports = {
  jsTask,
};

Gulp 案例

接下来,我们编写一个案例,通过 gulp 来开启本地服务和打包:

打包 html 文件;

  • 使用 gulp-htmlmin 插件;

使用 gulp-htmlmin 插件;

  • 使用 gulp-babel,gulp-terser 插件;

打包 less 文件;

  • 使用 gulp-less 插件;

html 资源注入

  • 使用 gulp-inject 插件;

开启本地服务器

  • 使用 browser-sync 插件;

创建打包任务

创建开发任务

package.json

json
"scripts": {
  "build": "gulp buildTask", // 打包任务
  "serve": "gulp serveTask"  // 开发任务
}
javascript
const { src, dest, parallel, series, watch } = require("gulp");

const htmlmin = require("gulp-htmlmin");
const babel = require("gulp-babel");
const terser = require("gulp-terser");
const less = require("gulp-less");

const inject = require("gulp-inject");
const browserSync = require("browser-sync");

// 1.对html进行打包
const htmlTask = () => {
  return src("./src/**/*.html")
    .pipe(htmlmin({ collapseWhitespace: true }))
    .pipe(dest("./dist"));
};

// 2.对JavaScript进行打包
const jsTask = () => {
  return src("./src/**/*.js")
    .pipe(babel({ presets: ["@babel/preset-env"] }))
    .pipe(terser({ toplevel: true }))
    .pipe(dest("./dist"));
};

// 3.对less进行打包
const lessTask = () => {
  return src("./src/**/*.less").pipe(less()).pipe(dest("./dist"));
};

// 4.在html中注入js和css
const injectTask = () => {
  return src("./dist/**/*.html")
    .pipe(
      inject(src(["./dist/**/*.js", "./dist/**/*.css"]), { relative: true })
    )
    .pipe(dest("./dist"));
};

// 5.开启一个本地服务器
const bs = browserSync.create();
const serve = () => {
  watch("./src/**", buildTask);

  bs.init({
    port: 8080,
    open: true,
    files: "./dist/*",
    server: {
      baseDir: "./dist",
    },
  });
};

// 创建项目构建的任务
const buildTask = series(parallel(htmlTask, jsTask, lessTask), injectTask);
const serveTask = series(buildTask, serve);
// webpack搭建本地 webpack-dev-server

module.exports = {
  buildTask,
  serveTask,
};

打包后的 html 想要注入 js 和 css,打包之前的 index.html 需要做如下配置,gulp 才知道将 js 和 css 注入到哪个位置。

index.html

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Gulp Projecct</title>
    <!-- inject:css -->
    <!-- endinject -->
  </head>
  <body>
    <!-- inject:js -->
    <!-- endinject -->
  </body>
</html>
Gulp Projecct