gulp 的使用(六):利用 webpack 流实现模块化

这是我参与更文挑战的第19天,活动详情查看: 更文挑战

前言

在上一篇文章 gulp 的使用(五):处理图片 里,介绍了如何使用 gulp 处理图片。

在这篇文章里,将介绍在使用 gulp 的同时,如何去使用 webpack 去处理模块化(当然也可以利用 webpack 做其他事情)。

没错,gulp 与 webpack 皆可兼得,我全都要!!!

image.png

但是要注意,虽然可以这样做,但我并不建议,如果项目需要模块化,不要用 gulp,请直接使用 webpack !!!

使用 webpack 流

在第一篇文章 gulp 的使用(一):起步 里,我就已经说过了 gulp 自身无法处理模块化,它只是一个基于流的自动化构建工具,如果要处理模块化,它可以利用 webpack 来处理模块化部分。

webpack-stream 其实就是一个 webpack,不过它与普通的 webpack 不同,它是以流的形式存在,与 gulp 集成。

安装 webpack-stream

npm i -D webpack-stream
复制代码

在项目根目录里创建 webpack 配置文件 webpack.config.js,写上以下基础代码。

console.log('利用了 webpack !!!')

module.exports = {
  mode: 'development',
  devtool: 'eval-cheap-module-source-map',
  module: {
    rules: [
      
    ]
  },
  resolve: {
    alias: {

    }
  },
}
复制代码

在 gulpfile.js 引入 webpack-stream 和 webpack.config.js,并且修改 js 任务。

const webpack = require('webpack-stream')
const webpackConfig = require("./webpack.config.js")

...

function js() {
  return src(['src/js/**/*.js'])
    .pipe(changed('dist/js/**/'))
    .pipe(webpack(webpackConfig))
    .pipe(plumber())
    .pipe(uglify())
    .pipe(dest('dist/js'))
}
复制代码

我们再在 src/js 目录创建 utils.js,写上以下测试代码。

export default {
  test() {
    console.log('测试模块化成功!!!')
  }
}
复制代码

在 index.js 使用 es6 的 import 引入 utils.js。

function print() {
  console.log('测试')
}

print()

import utils from './utils'

utils.test()
复制代码

运行 npm run dev,打开网站,打开控制台。

image.png

咦?为什么没有值?不单止是 utils.test() 的内容没有输出来,index.js 自身的 console.log 的内容也没有输出来。

我们再看看终端,webpack 的配置确实是被加载了。

image.png

别急,我们再看看 dist/js 目录,可以发现原本应该命名为 index.js 的文件,被打包成了以一串随机字符串为文件名的文件,而我们的 index.html 引用的是以 index.js 为文件名的文件。

image.png

而且你会发现无论你的项目里有多少个 js,打包出来的始终只有一个 js,那是因为 webpack 在默认配置里使用的是单页面模式去打包。

熟悉 webpack 的小伙伴可能会说,这是因为我没有配置 webpack 的 entry 和 output,的确我没有进行配置,这也是一种方法,也有另一种方法可以解决,各有优缺点,下面逐一介绍。

如何开发多页面

方法1:配置 webpack 的 entry 和 output

我们只要在 webpack.config.js 配置上 entry 和 output 就可以了。

console.log('利用了 webpack !!!')

const path = require('path')

module.exports = {
  mode: 'development',
  devtool: 'eval-cheap-module-source-map',
  entry: {
    index: path.resolve(__dirname, './src/js/index.js'),
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, './dist'),
  },
  module: {
    rules: [
      
    ]
  },
  resolve: {
    alias: {

    }
  },
}
复制代码

不过,你有没有发现如果这样做,以后页面每需要引入一个 js,都需要终端当前的 dev 服务,再在 webpack 里写好新加入的 entry,然后再启动 dev 服务测试。

也许你会想到可以用以下代码进行监听 webpack 配置,每当 webpack 配置被修改,都会去执行 js 任务。

watch('webpack.config.js', series(js))
复制代码

不过经过实际试验,这是行不通的,webpack 配置只有第一次任务执行才会被加载。

那么该怎么办呢?我们可以使用另一种方法

方法2:使用 vinyl-named 插件

vinyl-named 插件可以解决多页面开发的问题。

安装 vinyl-named

npm i -D vinyl-named
复制代码

配置 gulpfile.js,引入 vinyl-named。

const named = require('vinyl-named')
const path = require('path')

...

function js() {
  return src(['src/js/**/*.js'])
    .pipe(changed('dist/js/**/'))
    .pipe(named(function (file) {
      return file.relative.slice(0, -path.extname(file.path).length)
    }))
    .pipe(webpack(webpackConfig))
    .pipe(plumber())
    .pipe(uglify())
    .pipe(dest('dist/js'))
}
复制代码

运行 npm run dev,当我们在 src/js 里新增 js 时,你会发现 dist/js 目录里会自动生成对应的 js 文件。

注意,不要直接写上 named(),这样写的话,如果 js 有一个目录 aa,aa 目录里面有一个 aa.js,打包出来的 aa.js 是位于 dist/js/aa.js,而不是在 dist/js/aa/aa.js,少了一个 aa 目录

function js() {
  return src(['src/js/**/*.js'])
    .pipe(changed('dist/js/**/'))
    /*.pipe(named(function (file) {
      return file.relative.slice(0, -path.extname(file.path).length)
    })) */
    .pipe(named()) // 不要这样写
    .pipe(webpack(webpackConfig))
    .pipe(plumber())
    .pipe(uglify())
    .pipe(dest('dist/js'))
}
复制代码

当然,这种方法也有自身的缺点,你有没有发现作为模块的 utils.js 也被打包出来了,实际上 utils.js 并不需要打包出来,index.js 已经包含了需要的 utils.js 代码。如果你用的是 webpack 的 entry-output 方法,你会发现 utils.js 是不会打包出来。

请注意此项目将使用 vinyl-named 插件来处理多页面。

结语

webpack-stream 只是其中一种方式,还有 browserify 插件可以令 gulp 项目实现模块化,不过这种方式我没用过,听说有坑。

要弄模块化,我还是比较建议直接使用 webpack,不要使用这种以 gulp 为主,webpack 为辅的方式。因为这种开发方式很不伦不类,核心问题在于 webpack 构建打包出来的东西,gulp 怎么在任务去感知并进行关联。上面的 js 打包问题是因为有 vinyl-named 插件的存在得以解决,但是当你的项目越来越大,需要使用 webpack 的代码分离进行优化的时候,你会遇到很棘手的 html 与 js 的如何关联问题。如果我们都用 webpack 去进行管理 html,进行如何去关联 js,那么为什么我们不一开始就直接使用 webpack 去构建项目呢?

完整代码

目录

image.png

gulpfile.js

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

const Path = require('path')

const htmlmin = require('gulp-htmlmin')
const fileinclude = require('gulp-file-include')
const changed = require('gulp-changed')
const webserver = require('gulp-webserver')
const del = require('del')
const uglify = require('gulp-uglify')
const plumber = require('gulp-plumber')
const cleanCss = require('gulp-clean-css')
const sass = require('gulp-sass')
const fiber = require('fibers') // 变量名命名没 s
const imagemin = require('gulp-imagemin')
const cache = require('gulp-cache')
const webpack = require('webpack-stream')
const webpackConfig = require("./webpack.config.js")
const named = require('vinyl-named')
const path = require('path')

sass.compiler = require('sass')
//sass.compiler = require('node-sass')

function html() {
  return src(['src/**/*.html', '!src/include/**.html'])
    .pipe(changed('dist'))
    .pipe(plumber())
    .pipe(fileinclude({
      prefix: '@@', //引用符号
      basepath: './src/include', //引用文件路径
    }))
    .pipe(htmlmin({
      removeComments: true, //清除HTML注释
      collapseWhitespace: true, //压缩HTML
      collapseBooleanAttributes: true, //省略布尔属性的值 <input checked="true"/> ==> <input />
      removeEmptyAttributes: true, //删除所有空格作属性值 <input id="" /> ==> <input />
      removeScriptTypeAttributes: true, //删除<script>的type="text/javascript"
      removeStyleLinkTypeAttributes: true, //删除<style>和<link>的type="text/css"
      minifyJS: true, //压缩页面JS
      minifyCSS: true //压缩页面CSS
    }))
    .pipe(dest('dist'))
}

function js() {
  return src(['src/js/**/*.js'])
    .pipe(changed('dist/js/**/'))
    .pipe(named(function (file) {
      return file.relative.slice(0, -path.extname(file.path).length)
    }))
    .pipe(webpack(webpackConfig))
    .pipe(plumber())
    .pipe(uglify())
    .pipe(dest('dist/js'))
}

function libJs() {
  return src(['src/lib/**/*.js'])
    .pipe(changed('dist/lib/**/'))
    .pipe(plumber())
    .pipe(dest('dist/lib'))
}

function css() {
  return src(['src/css/**/*.css'])
    .pipe(changed('dist/css/**/'))
    .pipe(plumber())
    .pipe(cleanCss())
    .pipe(dest('dist/css'))
}

function scss() {
  return src(['src/scss/**/*.scss'])
    .pipe(changed('dist/scss/**/'))
    .pipe(plumber())
    .pipe(sass({ fiber }))
    .pipe(cleanCss())
    .pipe(dest('dist/scss'))
}

function libCss() {
  return src(['src/libCss/**/*.css'])
    .pipe(changed('dist/libCss/**/'))
    .pipe(plumber())
    .pipe(dest('dist/libCss'))
}

function img() {
  return src(['src/assets/img/**/*.{png,jpg,gif,jpeg,ico}']) //后缀都用小写,不然不识别
    .pipe(
      cache(
        imagemin({
          optimizationLevel: 5, //类型:Number  默认:3  取值范围:0-7(优化等级)
          progressive: true, //类型:Boolean 默认:false 无损压缩jpg图片
          interlaced: true, //类型:Boolean 默认:false 隔行扫描gif进行渲染
          multipass: true //类型:Boolean 默认:false 多次优化svg直到完全优化
        })
      )
    )
    .pipe(dest('dist/assets/img'))
}

function devServer() {
  return src('dist').pipe(webserver({
    port: 3000,
    livereload: true, // 是否实时加载
    // directoryListing: true, // 是否开启浏览目录
    // open: true, // 是否自动打开
    // proxies: [ // 代理,可以用来解决跨域问题
    //   {source: '/api', target: 'http://xxxx.com', options: {headers: {"Content-Type": 'application/x-www-form-urlencoded'}}}
    // ]
  }))
}

function watcher() {
  watch('src/**/*.html', series(html)).on('unlink', function (path) {
    del('dist/**/*.html' + Path.basename(path))
  })
  watch('src/js/**/*.js', series(js)).on('unlink', function (path) {
    del('dist/js/**/*.js' + Path.basename(path))
  })
  watch('src/lib/**/*.js', series(libJs)).on('unlink', function (path) {
    del('dist/lib/**/*.js' + Path.basename(path))
  })
  watch('src/css/**/*.css', series(css)).on('unlink', function (path) {
    del('dist/css/**/*.css' + Path.basename(path))
  })
  watch('src/scss/**/*.scss', series(scss)).on('unlink', function (path) {
    del('dist/scss/**/*.css' + Path.basename(path))
  })
  watch('src/libCss/**/*.css', series(libCss)).on('unlink', function (path) {
    del('dist/libCss/**/*.css' + Path.basename(path))
  })
  watch('src/assets/img/**/*.{png,jpg,gif,jpeg,ico}', series(img)).on('unlink', function (path) {
    del('dist/assets/img/**/*.{png,jpg,gif,jpeg,ico}' + Path.basename(path))
  })
}

function clean() {
  return del('dist')
}

exports.default = series(clean, html, libJs, js, css, scss, libCss, img, devServer, watcher)
exports.build = series(clean, html, libJs, js, css, scss, libCss, img)
复制代码

webpack.config.js

console.log('利用了 webpack !!!')

const path = require('path')

module.exports = {
  mode: 'development',
  devtool: 'eval-cheap-module-source-map',
  module: {
    rules: [
      
    ]
  },
  resolve: {
    alias: {

    }
  },
}
复制代码

index.js

function print() {
  console.log('测试')
}

print()

import utils from './utils'

utils.test()
复制代码

utils.js

export default {
  test() {
    console.log('测试模块化成功!!!')
  }
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享