Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

放弃webpack,拥抱gulp #29

Open
maicFir opened this issue Aug 11, 2022 · 0 comments
Open

放弃webpack,拥抱gulp #29

maicFir opened this issue Aug 11, 2022 · 0 comments

Comments

@maicFir
Copy link
Owner

maicFir commented Aug 11, 2022

别被标题吓到,哈哈,即使现在vite横空出世,社区光芒四射,两个字很快,但是webpack依旧宝刀未老,依然扛起前端工程化的大梁,但是今天我为啥说要拥抱gulp,因为我们经常常吃一道菜,所以要换个口味,这样才营养均衡。

gulp定义是:用自动化构建工具增强你的工作流程,是一种基于任务文件流方式,你可以在前端写一些自动化脚本,或者升级历史传统项目,解放你重复打包,压缩,解压之类的操作。

个人理解gulp是一种命令式编程的体验,更注重构建过程,所有的任务需要你自己手动创建,你会对构建流程会非常清楚,这点不像webpackwebpack就是一个开箱即用的声明式方式,webpack是一个模块化打包工具,内部细节隐藏非常之深,你也不需关注细节,你只需要照着提供的API以及引入对应的loaderplugin使用就行。

言归正传,为了饮食均衡,今天一起学习下gulpjs

正文开始...

搭建一个简单的前端应用

相比较webpack,其实gulp的项目结构更偏向传统的应用,只是我们借助gulp工具解放我们的一些代码压缩es6编译打包以及在传统项目中都可以使用less体验。

gulp目录下新建01-simple-demo

根目录下生成默认package.json

npm init -y

然后在public目录下新建imagescssjsindex.html

文件结构,大概就这样

然后在安装gulp

npm i gulp --save-dev

在根目录下新建gulpfile.js
我们先在gulpfile.js中写入一点内容,测试一下

const defaultTask = (cb) => {
  console.log('hello gulp');
  cb();
};
exports.default = defaultTask;

然后我们在命令行执行

npx gulp

当我们执行npx gulp时会默认运行gulpfile.js导出的default,在gulpfile.js导出的任务会注册到gulp任务中

gulp中任务主要分两种,一种是公开任务、另一种是私有任务

公开任务可以直接在命令执行npx gulp xxx调用执行,比如下面的defaultTask就是一个公开任务,只要被导出就是一个公开任务,没有被导出就是一个私有任务。

...
exports.default = defaultTask;

公有任务taskJS

// gulpfile.js
const { src, dest } = require('gulp');
const pathDir = (dir) => {
  return path.resolve(__dirname, dir);
};
// todo 执行ts任务,将js目录下的js打包到dist/js目录下
const taskJS = () => {
  return src(pathDir('public/**/*.js'), { sourcemaps: true }).pipe(dest(pathDir('dist/js')));
};
exports.taskJS = taskJS;

然后你在命令行执行

npx gulp taskJS



至此你会发现dist目录下就有生成的js

安装 less

npm i less gulp-less --save-dev

css/index.less中写入测试 css 的代码

@bgcolor: yellow;
@defaultsize: 20px;
body {
  background-color: @bgcolor;
}
h1 {
  font-size: @defaultsize;
}

gulpfile.js中写入编译less的任务,需要gulp-less

const { src, dest } = require('gulp');
const less = require('gulp-less');
const pathDir = (dir) => {
  return path.resolve(__dirname, dir);
}
...
// todo less任务
const taskLess = () => {
  // css目录洗的所有.less文件,dest输出到dist/css目录下
  return src(pathDir('public/css/*.less')).pipe(less()).pipe(dest(pathDir('dist/css')))
}
exports.taskLess = taskLess;

命令行运行npx gulp taskLess,结果如下

图片资源

使用一个gulp-image插件对图片进行无损压缩处理

// gulpfile.js
const { src, dest } = require('gulp');
const image = require('gulp-image');
const path = require('path');
const pathDir = (dir) => {
  return path.resolve(__dirname, dir);
}
...
// todo 图片资源
const taskImage = () => {
  return src(pathDir('public/images/*.*')).pipe(image()).pipe(dest(pathDir('dist/images')))
}
exports.taskImage = taskImage;

一顿操作发现,最新版本不支持esm,所以还是降低版本版本,这里降低到6.2.1版本

然后运行npx gulp taskImage

图片压缩得不小

在这之前,我们分别定义了三个不同的任务,gulp导出的任务有
公开任务和私有任务,多个公开任务可以串行组合使用

组合任务 series 与 parallel

因此我可以将之前的介个任务组合在一起

// gulpfile.js
const { src, dest, series } = require('gulp');
const less = require('gulp-less');
const image = require('gulp-image');
const path = require('path');
const pathDir = (dir) => {
  return path.resolve(__dirname, dir);
}
// todo js任务
const taskJS = () => {
  return src(pathDir('public/**/*.js'), { sourcemaps: true }).pipe(dest(pathDir('dist/js')))
}
...
// series组合多个任务
const seriseTask = series(taskJS, taskLess, taskLess, taskImage)
exports.seriseTask = seriseTask;

当我在命令行npx gulp seriseTask

已经在dist生成对应的文件了

编译转换 es6

在我们index.js,很多时候是写的es6,在gulp中我们需要一些借助一些插件gulp-babel,另外我们需要安装另外两个babel核心插件@babel/core,@babel/preset-env

 npm i gulp-babel @babel/core @babel/preset-env

gulpfile.js中我们需要修改下

...
const babel = require('gulp-babel');
// todo js任务
// 用babel转换es6语法糖
const taskJS = () => {
  return src(pathDir('public/**/*.js'), { sourcemaps: true }).pipe(babel({
    presets: ['@babel/preset-env']
  })).pipe(dest(pathDir('dist/js')))
}

当我们在js/index.js写入一段测试代码

js / index.js;
const appDom = document.getElementById('app');
appDom.innerHTML = 'hello gulp';
const fn = () => {
  console.log('公众号:Web技术学苑,好好学习,天天向上');
};
fn();

运行npx gulp seriseTask

箭头函数和const申明的变量就变成了es5

通常情况下,一般打包后的dist下的css或者js都会被压缩,在gulp中也是需要借助插件来完成

压缩 js 与 css

压缩js

...
const teser = require('gulp-terser');
// todo js任务
const taskJS = () => {
  return src(pathDir('public/**/*.js'), { sourcemaps: true }).pipe(babel({
    presets: ['@babel/preset-env']
  })).pipe(teser({
    mangle: {
      toplevel: true // 混淆代码
    }
  })).pipe(dest(pathDir('dist/js')))
}
...

压缩css

...
const uglifycss = require('gulp-uglifycss');
// todo less任务
const taskLess = () => {
  return src(pathDir('public/css/*.less')).pipe(less()).pipe(uglifycss()).pipe(dest(pathDir('dist/css')))
}
...

在这之前我们在输出dest时候我们都执向了一个具体的文件目录,在src这个api中是创建流,从文件中读取vunyl对象,本身也提供了一个base属性,因此你可以像下面这样写

const { src, dest, series } = require('gulp');
const less = require('gulp-less');
const image = require('gulp-image');
const babel = require('gulp-babel');
const teser = require('gulp-terser');
const uglifycss = require('gulp-uglifycss');
const path = require('path');
const pathDir = (dir) => {
  return path.resolve(__dirname, dir);
};
// 设置base,当输出文件目标dist文件时,会自动拷贝当前文件夹到目标目录
const basePath = {
  base: './public'
};
// todo js任务
const taskJS = () => {
  return src(pathDir('public/**/*.js', basePath))
    .pipe(
      babel({
        presets: ['@babel/preset-env']
      })
    )
    .pipe(
      teser({
        mangle: {
          toplevel: true // 混淆代码
        }
      })
    )
    .pipe(dest(pathDir('dist')));
};
// todo less任务
const taskLess = () => {
  return src(pathDir('public/css/*.less'), basePath)
    .pipe(less())
    .pipe(uglifycss())
    .pipe(dest(pathDir('dist')));
};
// todo 图片资源,有压缩,并输出到对应的dist/images文件夹下
const taskImage = () => {
  return src(pathDir('public/images/*.*'), basePath)
    .pipe(image())
    .pipe(dest(pathDir('dist')));
};
// todo html
const taskHtml = () => {
  return src(pathDir('public/index.html'), basePath).pipe(dest(pathDir('dist')));
};
const defaultTask = (cb) => {
  console.log('hello gulp');
  cb();
};

// series组合多个任务
const seriseTask = series(taskHtml, taskJS, taskLess, taskLess, taskImage);

exports.default = defaultTask;
exports.taskJS = taskJS;
exports.taskLess = taskLess;
exports.taskImage = taskImage;
exports.seriseTask = seriseTask;

将资源注入 html 中

gulp中,任务之间的依赖关系需要我们自己手动写一些执行任务流,现在一些打包后的dist的文件并不会自动注入html中。

参考gulp-inject

...
const inject = require('gulp-inject');
...
// 将css,js插入html中
const injectHtml = () => {
  // 目标资源
  const targetSources = src(['./dist/**/*.js', './dist/**/*.css'], { read: false });
  // 目标html
  const targetHtml = src('./dist/*.html')
  // 把目标资源插入目标html中,同时输出到dist文件下
  const result = targetHtml.pipe(inject(targetSources)).pipe(dest('dist'));
  return result
}
// series串行组合多个任务
const seriseTask = series(taskHtml, taskJS, taskLess, taskLess, taskImage, injectHtml)

exports.seriseTask = seriseTask;

注意一个执行顺序,必须是等前面任务执行完了,再注入,所以在series任务的最后才执行injectHtml操作

并且在public/index.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</title>
    <!-- inject:css -->
    <!-- endinject -->
  </head>
  <body>
    <div id="app"></div>
    <!-- inject:js -->
    <!-- endinject -->
  </body>
</html>

当我们运行npx gulp seriseTask

创建本地服务

参考browser-sync

const { src, dest, series, watch } = require('gulp');
const browserSync = require('browser-sync');
...

const taskBuild = seriseTask;
// 本地服务
const taskDevServer = () => {
  // 监听public所有目录下,只要文件发生改变,就重新加载
  watch(pathDir('public'), taskBuild);
  // 创建服务
  const server = browserSync.create();
  // 调用init开启端口访问
  server.init({
    port: '8081', //设置端口
    open: true,  // 自动打开浏览器
    files: './dist/*', // dist文件
    server: {
      baseDir: './dist'
    }
  })
}
exports.taskDevServer = taskDevServer;

当我们运行npx gulp taskDevServer时,浏览器会默认打开http://localhost:8081

我们使用了一个watch监听public目录下的所有文件,如果文件有变化时,会执行taskBuild任务会在dist目录下生成对应的文件,然后会启动一个本地服务,打开一个8081的端口就可以访问应用了。

至此一个一个用gulp搭建的前端应用终于可以了。

重新组织 gulpfile

最后我们可以再重新组织一下gulpfile.js,因为多个任务写在一个文件里貌似不太那么好维护,随着业务迭代,会越来越多,因此,有必要将任务分解一下

在根目录新建task,我们把所有的任务如下

common.js

// task/common.js
const path = require('path');
const pathDir = (dir) => {
  return path.join(__dirname, '../', dir);
};
const rootDir = path.resolve(__dirname, '../');
const basePath = {
  base: './public'
};
const targetDest = 'dist';
module.exports = {
  rootDir,
  pathDir,
  basePath,
  targetDest
};

injectHtml.js

// task/injectHtml.js
const { src, dest } = require('gulp');
const inject = require('gulp-inject');
const { targetDest, rootDir } = require('./common.js');
// 将css,js插入html中
const injectHtml = () => {
  // 目标资源
  const targetSources = src([`${rootDir}/${targetDest}/**/*.js`, `${rootDir}/${targetDest}/**/*.css`]);
  // 目标html
  const targetHtml = src(`${rootDir}/${targetDest}/*.html`);
  // 把目标资源插入目标html中,同时输出到dist文件下
  const result = targetHtml.pipe(inject(targetSources, { relative: true })).pipe(dest(targetDest));
  return result;
};
module.exports = injectHtml;

taskDevServer.js

const { watch } = require('gulp');
const path = require('path');
const browserSync = require('browser-sync');
const { pathDir, targetDest, rootDir } = require('./common.js');
const taskDevServer = (taskBuild) => {
  return (options = {}) => {
    const defaultOption = {
      port: '8081', //设置端口
      open: true, // 自动打开浏览器
      files: `${rootDir}/${targetDest}/*`, // 当dist文件下有改动时,会自动刷新页面
      server: {
        baseDir: `${rootDir}/${targetDest}` // 基于当前dist目录
      },
      ...options
    };
    // 监听public所有目录下,只要文件发生改变,就重新加载
    watch(pathDir('public'), taskBuild);
    const server = browserSync.create();
    server.init(defaultOption);
  };
};
module.exports = taskDevServer;

...

task/index.js

const injectHtml = require('./injectHtml.js');
const taskDevServer = require('./taskDevServer.js');
const taskHtml = require('./taskHtml.js');
const taskImage = require('./taskImage.js');
const taskJS = require('./taskJS.js');
const taskLess = require('./taskLess.js');
module.exports = {
  injectHtml,
  taskDevServer,
  taskHtml,
  taskImage,
  taskJS,
  taskLess
};

gulpfile.js中,我们修改下

// gulpfile.js
const { series } = require('gulp');
const { injectHtml, taskDevServer, taskHtml, taskImage, taskJS, taskLess } = require('./task/index.js');

// series组合多个任务
const seriseTask = series(taskHtml, taskJS, taskLess, taskLess, taskImage, injectHtml);
// 本地服务
const devServer = taskDevServer(seriseTask);
// 启动服务
const server = () => {
  devServer({
    port: 9000
  });
};
const taskBuild = seriseTask;
const defaultTask = (cb) => {
  console.log('hello gulp');
  cb();
};
exports.default = defaultTask;
exports.server = server;
exports.build = taskBuild;

我们在package.json中新增命令

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "server": "gulp server",
    "build": "gulp build"
  },

npm run build

在启动server之前,我们先执行npm run build,然后再执行下面命令,保证browserSync创建的服务文件夹存在,不然页面打开就404错误

npm run server

至此gulp搭建一个简单的应该就已经完全 ok 了

页面背景貌似有点黄

总结

  • gulpjs开发是一个任务流的开发方式,它的核心思想就是用自动化构建工具增强你的工作流,所有的自动化工作流操作都牢牢的掌握在自己手上,你可以用gulp写一些自动化脚本,比如,文件上传,打包,压缩,或者改造传统的前端应用。

  • gulp写了一个简单的应用,但是发现中途需要找好多gulp插件,gulp的生态还算可以,3w多个 star,生态相对丰富,但是有些插件常年不更新,或者版本更新不支持,比如gulp-image,当你按照官方文档使用最新的包时,不支持 esm,你必须降低版本6.2.1,改用cjs才行

  • 使用gulp的一些常用的 api,比如srcdestseries,以及browser-sync实现本地服务,更多api参考官方文档。

  • 即使项目时间再多,也不要用gulp搭建前端应用,因为webpack生态很强大了,看gulp的最近更新还是 2 年前,但是写个自动化脚本,还算可以,毕竟gulp的理念就是用自动化构建工具增强你工作流程,也许当你接盘传统项目时,一些打包,拷贝,压缩文件之类的,可以尝试用用这个。

  • 本文示例code-example

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant