《记一忘三二》前端构建工具学习

52 阅读7分钟

Scripts构建

image-20250305110829058

sass编译

首先需要将./scss/style.scss转为./dist/style.css

# 安装sass编译工具
pnpm i sass -D
# 使用sass编译工具
npx sass ./scss/style.scss ./dist/style.css

但这样在执行编译命令时,需要记住目录、配置等编译参数

{
    "scripts": {
        "build": "sass ./scss/style.scss ./dist/style.css"
    }, 
    "devDependencies": {
        "sass": "^1.85.1"
    }
}

配置scripts能够给长命令取一个命令别名,这样只需要执行这个命令别名就可以了

# 执行scripts的命令
npx run build

browser-sync服务

开启web服务器

# 安装browser-sync
npm i browser-sync -D
#  启动web服务器
npx browser-sync ./

命令在scripts中配置命令别名

{
    "scripts": {
        "build": "sass ./scss/style.scss ./dist/style.css",
        "browser": "browser-sync ./"
    },
    "devDependencies": {
        "browser-sync": "^3.0.3",
        "sass": "^1.85.1"
    }
}

**&&**串行

build命令browser命令都是需要单独执行的,但是sass编译是项目启动的前提,也就是说在执行browser命令时,都需要提前执行build命令

通过添加serve命令,使用**&&实现串行**build命令browser命令

{
    "scripts": {
        "build": "sass ./scss/style.scss ./dist/style.css",
         "browser": "browser-sync ./ --files \"dist/*.css\"",
        "serve": "npm run build && npm run browser"
    },
    "devDependencies": {
        "browser-sync": "^3.0.3",
        "sass": "^1.85.1"
    }
}

sass编译添加--watch

{
    "scripts": {
        "build": "sass ./scss/style.scss ./dist/style.css --watch",
        "browser": "browser-sync ./",
        "serve": "npm run build && npm run browser"
    },
    "devDependencies": {
        "browser-sync": "^3.0.3",
        "sass": "^1.85.1"
    }
}

单独执行build命令并没有发生问题,并且scss文件也能实现监听并且改变

但是去执行serve命令,会发现build命令之后,控制台就停顿了,后续未执行browser命令

image-20250305122755564

npm-run-all

因为--watch需要单独开启一个控制台, build命令browser命令串行显然是行不通的

npm install npm-run-all --save-dev
{
    "scripts": {
        "build": "sass ./scss/style.scss ./dist/style.css --watch",
        "browser": "browser-sync ./ --files \"dist/*.css\"",
        "serve": "run-p  build browser"
    },
    "devDependencies": {
        "browser-sync": "^3.0.3",
        "npm-run-all": "^4.1.5",
        "sass": "^1.85.1"
    }
}

run-p能够并行执行build命令browser命令

Grunt工作流

基础使用

# 安装grunt
npm i grunt -D

根目录创建gruntfile.js文件

module.exports = (grunt) => {
    grunt.registerTask("task", () => {
        console.log("task")
    })
}
# 执行 task 任务
npx grunt task

定义任务

grunt.registerTask(任务名称, 任务函数)

同步任务

module.exports = (grunt) => {
    grunt.registerTask("task", () => {
        console.log("task")
    })
}
# 执行 task 任务
npx grunt task

异步任务

grunt.registerTask("async-task", function () {
    const done = this.async()
    setTimeout(() => {
        console.log("async-task")
        done()
    }, 1000)
})

done函数被调用,表示异步任务执行完毕

注:使用this关键生成done函数,所以不能使用箭头函数

# 执行 task 任务
npx grunt async-task

默认任务


module.exports = (grunt) => {
    grunt.registerTask("task", () => {
        console.log("task")
    })

    grunt.registerTask("default", ["task"])
}
# 执行默认任务,会自动执行第二参数的全部任务名称
npx grunt 

标记任务失败

同步任务

module.exports = (grunt) => {
  grunt.registerTask('error-task', () => {
    return false
  })
}
# 执行 error-task 任务
npx grunt error-task

image-20250305175753042

异步任务

module.exports = (grunt) => {
  grunt.registerTask('async-error-task', function () {
    const done = this.async()
    setTimeout(() => {
      done(false)
    }, 1000)
  })
}
# 执行 async-error-task 任务
npx grunt async-error-task

多个任务

module.exports = (grunt) => {
  grunt.registerTask('error-task-1', () => {
    console.log('error-task-1')
    return false
  })
  grunt.registerTask('error-task-2', () => {
    console.log('error-task-2')
  })

  grunt.registerTask('default', ['error-task-1', 'error-task-2'])
}
npx grun

当前任务执行失败还会影响后续任务执行

image-20250306092527695

npx grun --force

--force能够忽略执行失败的任务,后续的任务会照常执行

执行方式

中途有任务标记失败,就会终止这次执行

module.exports = (grunt) => {
  // 定义同步任务 task-1
  grunt.registerTask('task-1', () => {
    console.log('task-1')
  })

  // 定义同步任务 task-2
  grunt.registerTask('task-2', function () {
    const done = this.async()
    setTimeout(() => {
      console.log('task-2')
      done()
    }, 1000)
  })

  // 定义同步任务 task-3
  grunt.registerTask('task-3', () => {
    console.log('task-3')
  })

  // 定义任务队列
  grunt.registerTask('default', ['task-1', 'task-2', 'task-3'])
}

同步串行

module.exports = (grunt) => {
  grunt.registerTask('hello', () => {
    console.log('Hello, Grunt!')
  })

  grunt.registerTask('world', () => {
    console.log('Hello, World!')
  })

  grunt.registerTask('say', () => {
    console.log('Hello, say!')
  })

  grunt.registerTask('parallel', ['hello', 'world', 'say'])
}

image-20250307145117049

异步串行

异步任务会等待上一个任务执行完毕

module.exports = (grunt) => {
  grunt.registerTask('async-task-1', function () {
    const done = this.async()
    setTimeout(() => {
      console.log('async-task-1')
      done()
    }, 1000)
  })

  grunt.registerTask('async-task-2', function () {
    const done = this.async()
    setTimeout(() => {
      console.log('async-task-2')
      done()
    }, 1000)
  })

  grunt.registerTask('async-task-3', function () {
    const done = this.async()
    setTimeout(() => {
      console.log('async-task-3')
      done()
    }, 1000)
  })

  grunt.registerTask('series', ['async-task-1', 'async-task-2', 'async-task-3'])
}

image-20250307145210384

配置参数

options

this.options()获取的配置参数,只要在initConfig配置的参数都可以按照路径进行访问

module.exports = (grunt) => {
  grunt.initConfig({
    build: {
      options: {
        soureMap: true,
      },
    },
  })

  grunt.registerTask('build', function () {
    const options = this.options()

    console.log(options)
  })
}

grunt.initConfig数据

grunt.initConfig配置的数据,可通过grunt.config('具体路径')获取数据

module.exports = (grunt) => {
  grunt.initConfig({
    js: './src/js/index.js',
    build: {
      css: './src/css/index.css',
    },
  })

  grunt.registerTask('build', () => {
    const js = grunt.config('js')
    const css = grunt.config('build.css')

    console.log(css, js)
  })
}

注: grunt.config能够获取options数据

多目标任务

module.exports = (grunt) => {
  grunt.initConfig({
    build: {
      options: {
        soureMap: true,
      },
      css: {
        options: {
          soureMap: false,
        },
        src: './css/index.css',
        dist: './css/index.css',
      },
      js: {
        src: './js/index.js',
        dist: './js/index.js',
      },
    },
  })

  grunt.registerMultiTask('build', function () {
    const options = this.options()
    const target = this.target
    const data = this.data

    console.log(target, options, data)
  })
}

一个任务有多个目标,也可以说一个目标下面的子任务

在执行build任务时,会执行cssjs两个子任务

# 执行 build 主任务
npx grunt build

image-20250306113351461

并且还可以单独子任务

# 执行 build:css 子任务
npx grunt build:css
# 执行 build:js 子任务
npx grunt build:js

插件使用

grunt-contrib-clean

# 安装 grunt-contrib-clean
pnpm  i grunt-contrib-clean -D

加载grunt-contrib-clean插件,并设置任务目标

module.exports = (grunt) => {
  grunt.initConfig({
    // 设置 grunt-contrib-clean 的目标
    clean: {
      dist: {
        src: ['./dist/**'],
      },
    },
  })

  // 加载 grunt-contrib-clean 插件
  grunt.loadNpmTasks('grunt-contrib-clean')
}
# 执行 clean 任务
npx grunt clean

注:一般情况下,grunt的插件名称是grunt-contrib-(插件任务名称)

综合示例

# 安装依赖
npm install grunt-contrib-clean grunt-contrib-uglify @babel/core @babel/preset-env grunt grunt-babel grunt-contrib-watch -D
module.exports = (grunt) => {
  grunt.initConfig({
    clean: {
      dist: {
        src: ['./dist/**'],
      },
    },
    uglify: {
      dist: {
        files: {
          './dist/index.js': './dist/index.js',
        },
      },
    },
    babel: {
      options: {
        presets: ['@babel/preset-env'],
      },
      dist: {
        files: {
          './dist/index.js': './src/index.js',
        },
      },
    },
    watch: {
      scripts: {
        files: ['src/**/*.js'],
        tasks: ['babel', 'uglify'],
        options: {
          spawn: false,
        },
      },
    },
  })

  grunt.loadNpmTasks('grunt-babel')
  grunt.loadNpmTasks('grunt-contrib-clean')
  grunt.loadNpmTasks('grunt-contrib-uglify')
  grunt.loadNpmTasks('grunt-contrib-watch')
  grunt.registerTask('default', ['clean', 'babel', 'uglify', 'watch'])
}

Gulp工作流

基础使用


npm i gulp -D

根目录创建gulpfile.js文件

exports.world = (done) => {
  console.log('world')
  done()
}

exports.asyncTask = (done) => {
  setTimeout(() => {
    console.log('asyncTask')
    done()
  })
}

exports.default = (done) => {
  console.log('default')
  done()
}
# 执行 world 任务
npx gulp world
# 执行 asyncTask 任务
npx gulp asyncTask
# 执行 default 任务,也就是默认任务
npx gulp

定义任务

任何任务都需要标记结束,通常使用done

默认任务

exports.default = (done) => {
  console.log('default')
  done()
}

同步任务

exports.world = (done) => {
  console.log('world')
  done()
}

异步任务

done
exports.asyncTask = (done) => {
  setTimeout(() => {
    console.log('asyncTask')
    done()
  })
}
Promise
exports.asyncTask = () => {
  console.log('asyncTask')
  return Promise.resolve()
}
async
exports.asyncTask = async () => {
  console.log('asyncTask')
}
on监听
const {
  src,
  dest,
} = require('gulp')

exports.copy_src = () => {
  return src('src/**').pipe(dest('dist'))
}

自动监听end事件进行任务结束判断

const {
  src,
  dest,
} = require('gulp')

exports.copy_src = (done) => {
  src('src/**').pipe(dest('dist')).on('end', done)
}

标记任务失败

done
exports.error_task = (done) => {
  done(new Error('error-task failed'))
}

exports.async_error_task = (done) => {
  setTimeout(() => {
    done(new Error('async-error-task failed'))
  }, 1000)
}
Promise
exports.error_task = () => {
  return Promise.reject(new Error('error-task failed'))
}
async
exports.error_task = async () => {
  throw new Error('error-task failed')
}

执行方式

异步串行

const { series } = require('gulp')

function async_task_1(done) {
  setTimeout(() => {
    console.log('async task 1')
    done()
  }, 1000)
}

function async_task_2(done) {
  setTimeout(() => {
    console.log('async task 2')
    done()
  }, 1000)
}

function async_task_3(done) {
  setTimeout(() => {
    console.log('async task 3')
    done()
  }, 1000)
}

exports.series = series(async_task_1, async_task_2, async_task_3)

image-20250307144547757

异步并行

const { parallel } = require('gulp')

function async_task_1(done) {
  setTimeout(() => {
    console.log('async task 1')
    done()
  }, 1000)
}

function async_task_2(done) {
  setTimeout(() => {
    console.log('async task 2')
    done()
  }, 1000)
}

function async_task_3(done) {
  setTimeout(() => {
    console.log('async task 3')
    done()
  }, 1000)
}

exports.parallel = parallel(async_task_1, async_task_2, async_task_3)

image-20250307144619816

同步串行

const { series } = require('gulp')

function task_1(done) {
  console.log('async task 1')
  done()
}

function task_2(done) {
  console.log('async task 2')
  done()
}

function task_3(done) {
  console.log('async task 3')
  done()
}

exports.series = series(task_1, task_2, task_3)

image-20250307144428981

异步并行

const { parallel } = require('gulp')

function task_1(done) {
  console.log('async task 1')
  done()
}

function task_2(done) {
  console.log('async task 2')
  done()
}

function task_3(done) {
  console.log('async task 3')
  done()
}

exports.parallel = parallel(task_1, task_2, task_3)

Webpack

devtool

当浏览器加载并执行一个包含sourceMappingURL注释的JavaScript文件时,会自动根据该注释中指定的目录地址加载对应的映射文件。浏览器将根据加载到的文件内容,生成源文件现文件之间的映射信息,从而实现源代码的调试功能

会根据不同的devtool生成不同的sourceMappingURL注释

方式

false

不生成映射注释

eval
//# sourceURL=webpack://my-webpack/./src/main.js?

浏览器不会加载映射文件,根据源文件的内容在src/main.js位置生成一份一样的文件,映射关系介绍行对行

image-20250322145523483

source-map
//# sourceMappingURL=bundle.js.map

浏览器加载映射文件bundle.js.map,生成映射关系源文件

image-20250322150302184

映射文件

{
    "version": 3,
    "file": "bundle.js",
    "mappings": ";;;;;;;;;;;;;;;;;AAAA,.....CAACT,gDAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,C",
    "sources": [
        "webpack://my-webpack/./src/utils/math.js",
        "webpack://my-webpack/webpack/bootstrap",
        "webpack://my-webpack/webpack/runtime/define property getters",
        "webpack://my-webpack/webpack/runtime/hasOwnProperty shorthand",
        "webpack://my-webpack/webpack/runtime/make namespace object",
        "webpack://my-webpack/./src/main.js"
    ],
    "sourcesContent": [
        "const sum = (a, b) => a + b;\r\n\r\nconst subtract = (a, b) => a - b;\r\n\r\nconst ...... \r\nconsole.log(sum(1, 2));"
    ],
    "names": [
        "sum",
        "a",
        "subtract",
        "b",
        "address"
    ],
    "sourceRoot": ""
}
  • version: 指定源码映射的版本,这里是第 3 版。第 2 版比第 1 版生成的映射文件大小减少%,第 3 版比第 2 版减少%
  • file: 构建后的文件名称 bundle.js
  • mappings: 包含了实际的映射关系,这是一个 Base64 编码的字符串,用于描述源文件和构建后文件之间的对应关系。这个字符串的具体含义需要通过解码和解析才能理解
  • sources: 包含了源文件的路径列表。这里列出了多个源文件,例如 src/utils/math.jssrc/main.js 等,这些文件可能通过 Webpack 等工具打包生成了 bundle.js
  • sourcesContent: 包含了源文件的内容
  • names: 包含了源文件中出现的变量和函数名称
  • sourceRoot:表示指定源文件的根目录