Gulp 用于 WordPress:创建任务

Avatar of Ali Alaa
Ali Alaa

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费信用额度!

这是关于为 WordPress 主题开发创建 Gulp 工作流程的两个部分系列中的第二篇文章。 第一部分重点介绍了在 WordPress 主题项目中 Gulp 的初始安装、设置和组织。 本文深入探讨了 Gulp 将运行的任务,通过分解每个任务的功能以及如何定制它们来简化主题开发。

现在我们已经完成了 本系列的第一部分,使用安装了 Gulp 的 WordPress 主题项目进行设置,是时候深入研究我们希望它在开发主题时为我们执行的任务了。 我们将在本文中亲自动手,准备好编写一些代码!

文章系列

  1. 初始设置
  2. 创建任务 (本文)

创建样式任务

让我们从将 src/bundle.scss 从 Sass 编译为 CSS 开始,然后将 CSS 输出缩小以用于生产模式,并将完成的 bundle.css 文件放入 dist 目录中。

我们将使用几个 Gulp 插件来完成繁重的工作。 我们将使用 gulp-sass 编译内容,并使用 gulp-clean-css 进行缩小。 然后,gulp-if 将允许我们有条件地运行函数,在这种情况下,它将检查我们是在生产模式还是开发模式下,然后再运行这些任务,然后相应地执行。

我们可以一步到位安装所有三个插件

npm install --save-dev gulp-sass gulp-clean-css gulp-if

让我们确保我们的 bundle.scss 文件中有一些内容,这样我们就可以测试这些任务

$colour: #f03;

body {
  background-color: $colour;
}

好的,回到 Gulpfile 中导入插件并定义运行它们的 task

import { src, dest } from 'gulp';
import yargs from 'yargs';
import sass from 'gulp-sass';
import cleanCss from 'gulp-clean-css';
import gulpif from 'gulp-if';
const PRODUCTION = yargs.argv.prod;

export const styles = () => {
  return src('src/scss/bundle.scss')
    .pipe(sass().on('error', sass.logError))
    .pipe(gulpif(PRODUCTION, cleanCss({compatibility:'ie8'})))
    .pipe(dest('dist/css'));
}

让我们逐步了解这段代码,解释一下发生了什么。

  • 来自 Gulp 的导入 src 和 dest 函数。 src 将读取您作为参数传递的文件并返回一个节点流。
  • 我们引入 yargs 来创建我们的标志,用于将任务分离到开发模式和生产模式之间。
  • 三个插件被调用到 action 中。
  • PRODUCTION 标志已定义并保存在 prod 命令中。
  • 我们将 styles 定义为我们将在命令行中用于运行这些任务的任务名称。
  • 我们告诉 task 我们想要处理哪个文件 (bundle.scss) 以及它位于哪里 (src/scss/bundle.scss)。
  • 我们创建了“管道”,这些管道充当在执行 styles 命令时运行的插头。 这些管道按编写顺序运行:将 Sass 转换为 CSS,缩小 CSS(如果我们处于生产模式),并将生成的 CSS 文件放入 dist/css 目录中。

来吧。 在命令行中运行 gulp styles 并查看您的 CSS 目录 dist/css 中是否添加了新的 CSS 文件。

现在执行 gulp styles --prod。 发生相同的事情,但是现在该 CSS 文件已被缩小,以便用于生产。

现在,假设您有一个带有 header.phpfooter.php 的功能正常的 WordPress 主题,则可以安全地将 CSS 文件(以及我们在处理这些任务时遇到的 JavaScript 文件)排队,很可能是在您的 functions.php 文件中

function _themename_assets() {
  wp_enqueue_style( '_themename-stylesheet', get_template_directory_uri() . '/dist/css/bundle.css', array(), '1.0.0', 'all' );
}
add_action('wp_enqueue_scripts', '_themename_assets');

这很好,但我们可以使我们的 style 命令更强大。

例如,尝试使用已激活的 WordPress 主题检查主页上的 body。 我们添加的样式应该在那里

如您所见,它表明我们的样式来自 bundle.css,这是真的。 但是,如果这里显示原始 SCSS 文件的名称,这会更好,因为我们的开发目的 — 它使查找代码变得更加容易,尤其是在我们使用大量部分时。 这就是 源地图 发挥作用的地方。 这将详细说明我们的样式在 DevTools 中的位置。 为了进一步说明这个问题,让我们在 src/scss/components/slider.scss 中添加一些 SCSS,然后将该文件导入 bundle.scss

//src/scss/components/slider.scss
body {
  background-color: aqua;
}
//src/scss/bundle.scss
@import './components/slider.scss';
$colour: #f03;
body {
  background-color: $colour;
}

再次运行 gulp styles 以重新编译您的文件。 然后,您的检查器应该如下所示

DevTools 检查器将显示两种样式都来自 bundle.css。 但我们希望它显示原始文件(即 bundle.scssslider.scss)。 因此,在我们开始编写代码之前,让我们将此添加到我们的改进愿望清单中。

我们还需要做的另一件事是为我们处理供应商前缀。 没有什么比必须自己编写和管理所有这些前缀更糟糕的了,而 Autoprefixer 是可以为我们执行此操作的工具。

并且,为了让 Autoprefixer 发挥作用,我们需要 PostCSS 插件。

好的,总共增加了三个插件和任务,我们需要运行。 让我们安装所有三个插件

npm install --save-dev gulp-sourcemaps gulp-postcss autoprefixer

所以 gulp-sourcemaps 显然将用于源地图。 gulp-postcssautoprefixer 将用于将自动前缀添加到我们的 CSS 中。 postcss 是一个用于转换 CSS 文件的著名插件,而 autoprefixer 只是 postcss 的一个插件。 您可以阅读有关使用 postcss 可以执行的其他操作的更多信息 这里

现在,让我们在最上面将我们的插件导入到 Gulpfile 中

import postcss from 'gulp-postcss';
import sourcemaps from 'gulp-sourcemaps';
import autoprefixer from 'autoprefixer';

然后让我们更新 task 以使用这些插件

export const styles = () => {
  return src('src/scss/bundle.scss')
    .pipe(gulpif(!PRODUCTION, sourcemaps.init()))
    .pipe(sass().on('error', sass.logError))
    .pipe(gulpif(PRODUCTION, postcss([ autoprefixer ])))
    .pipe(gulpif(PRODUCTION, cleanCss({compatibility:'ie8'})))
    .pipe(gulpif(!PRODUCTION, sourcemaps.write()))
    .pipe(dest('dist/css'));
}

要使用 sourcemaps 插件,我们必须遵循一些步骤

  1. 首先,我们使用 sourcemaps.init() 初始化插件。
  2. 接下来,将您想要映射的所有插件都通过管道传递。
  3. 最后,在将 bundle 写入目的地之前,通过调用 sourcemaps.write() 创建源映射文件。

请注意,在 sourcemaps.init()sourcemaps.write() 之间通过管道传递的所有插件都应与 gulp-sourcemaps 兼容。 在我们的例子中,我们使用的是 sass()postcss()cleanCss(),并且它们都 与 sourcemaps 兼容

请注意,我们只在生产标志之后运行 Autoprefixer,因为在开发过程中实际上不需要所有这些供应商前缀。

现在让我们运行 gulp styles,没有生产标志。 以下是 bundle.css 中的输出

body {
  background-color: aqua; }
body {
  background-color: #f03; }
/*#sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVuZGxlLmNzcyIsInNvdXJjZXMiOlsiYnVuZGxlLnNjc3MiLCJjb21wb25lbnRzL3NsaWRlci5zY3NzIl0sInNvdXJjZXNDb250ZW50IjpbIkBpbXBvcnQgJy4vY29tcG9uZW50cy9zbGlkZXIuc2Nzcyc7XG5cbiRjb2xvdXI6ICNmMDM7XG5ib2R5IHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAkY29sb3VyO1xufVxuOjpwbGFjZWhvbGRlciB7XG4gICAgY29sb3I6IGdyYXk7XG59IiwiYm9keSB7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogYXF1YTtcbn0iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFDQUEsQUFBQSxJQUFJLENBQUM7RUFDRCxnQkFBZ0IsRUFBRSxJQUFJLEdBQ3pCOztBRENELEFBQUEsSUFBSSxDQUFDO0VBQ0QsZ0JBQWdCLEVBRlgsSUFBSSxHQUdaOztBQUNELEFBQUEsYUFBYSxDQUFDO0VBQ1YsS0FBSyxFQUFFLElBQUksR0FDZCJ9 */#

下面的额外文本是源地图。 现在,当我们在 DevTools 中检查站点时,我们会看到

不错! 现在进入生产模式

gulp styles --prod

针对需要前缀的样式规则(例如 display: grid;)检查 DevTools,并确认它们都在那里。 并确保您的文件也已缩小。

关于此任务的最后一个提示。 假设我们想要多个 CSS bundle:一个用于前端样式,一个用于 WordPress 管理员样式。 我们可以在 src/scss 目录中创建一个新的 admin.scss 文件,并在 Gulpfile 中传递一个路径数组

export const styles = () => {
  return src(['src/scss/bundle.scss', 'src/scss/admin.scss'])
    .pipe(gulpif(!PRODUCTION, sourcemaps.init()))
    .pipe(sass().on('error', sass.logError))
    .pipe(gulpif(PRODUCTION, postcss([ autoprefixer ])))
    .pipe(gulpif(PRODUCTION, cleanCss({compatibility:'ie8'})))
    .pipe(gulpif(!PRODUCTION, sourcemaps.write()))
    .pipe(dest('dist/css'));
}

现在我们在 dist/css 目录中有了 bundle.cssadmin.css。 只要确保正确地排队所有这样分离的新的 bundle。

创建观察任务

好的,接下来是观察任务,它通过查找带有保存更改的文件,然后代表我们执行任务(而无需我们在命令行中自己调用它们)来使我们的生活变得更加轻松。 有多棒啊?

就像我们对 styles 任务所做的那样

import { src, dest, watch } from 'gulp';

我们将新任务称为 watchForChanges

export const watchForChanges = () => {
  watch('src/scss/**/*.scss', styles);
}

请注意,watch 不可用作名称,因为我们已经有一个使用它的变量。

现在让我们运行 gulp watchForChanges,命令行将始终监视 src/scss 目录中的任何 .scss 文件的更改。 并且,当这些更改发生时,styles 任务将立即运行,无需我们进一步操作。

请注意,src/scss/**/*.scss 是一个 glob 模式。 这基本上意味着此字符串将匹配 src/scss 目录或其中任何子文件夹中的任何 .scss 文件。 现在,我们只监视 .scss 文件并运行 styles 任务。 稍后,我们将扩展其范围以监视其他文件。

创建图像任务

正如我们之前所述,图像任务将压缩 src/images 中的图像,然后将它们移动到 dist/images 中。 让我们安装一个 gulp 插件,它将负责压缩图像

npm install --save-dev gulp-imagemin

现在,在 Gulpfile 的顶部导入此插件

import imagemin from 'gulp-imagemin';

最后,让我们编写图像任务

export const images = () => {
  return src('src/images/**/*.{jpg,jpeg,png,svg,gif}')
    .pipe(gulpif(PRODUCTION, imagemin()))
    .pipe(dest('dist/images'));
}

我们给src()函数一个通配符,它匹配src/images目录中的所有.jpg.jpeg.png.svg.gif图像。然后,我们运行imagemin插件,但只在生产环境中运行。压缩图像可能需要一些时间,在开发过程中并不必要,因此我们可以将其排除在开发流程之外。最后,我们将压缩后的图像版本放在dist/images中。

现在,当我们运行gulp images时,任何放入src/images的图像都会被复制。但是,运行gulp images --prod,将同时压缩和复制图像。

最后,我们需要做的就是修改我们的watchForChanges任务,将其中的watch包含图像。

export const watchForChanges = () => {
  watch('src/scss/**/*.scss', styles);
  watch('src/images/**/*.{jpg,jpeg,png,svg,gif}', images);
}

现在,假设watchForChanges任务正在运行,每当我们在src/images文件夹中添加图像时,图像任务就会自动运行。它为我们做了所有的事情!

重要提示:如果watchForChanges任务正在运行,并且修改了Gulpfile,则需要停止并重新启动它,以便更改生效。

创建复制任务

您可能遇到过这样的情况:您创建了文件,处理了它们,然后需要手动获取生产文件并将其放在它们需要的位置。正如我们在图像任务中看到的那样,我们可以使用复制功能来为我们完成此操作,并帮助防止移动错误文件。

export const copy = () => {
  return src(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'])
    .pipe(dest('dist'));
}

仔细阅读提供给src()的路径数组。我们告诉Gulp匹配srcsrc/**/*)中的所有文件和文件夹,除了图像、js和scss文件夹(!src/{images,js,scss})以及它们内部的任何文件或子文件夹(!src/{images,js,scss}/**/*)。

我们希望我们的watch任务也查找这些更改,因此我们将将其添加到混合中。

export const watchForChanges = () => {
  watch('src/scss/**/*.scss', styles);
  watch('src/images/**/*.{jpg,jpeg,png,svg,gif}', images);
  watch(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'], copy);
}

尝试将任何文件或文件夹添加到src目录,它应该被复制到/dist目录。但是,如果我们要在/images/js/scss内部添加文件或文件夹,它将被忽略,因为我们已经在单独的任务中处理了这些文件夹。

但是,我们这里仍然存在一个问题。尝试删除添加的文件,它不会发生。我们的任务只处理复制。这个问题也可能发生在我们的/images/js/scss文件夹中。如果我们有从src文件夹中删除的旧图像或JavaScript和CSS包,那么它们将不会从dist文件夹中删除。因此,最好在每次开始开发或构建主题时完全清理dist文件夹。这就是我们在下一个任务中要做的。

组合开发和构建任务

现在让我们安装一个负责删除dist文件夹的软件包。这个软件包叫做del

npm install --save-dev del

在顶部导入它

import del from 'del';

创建一个将删除dist文件夹的任务

export const clean = () => {
  return del(['dist']);
}

请注意,del返回一个promise。因此,我们不必调用cb()函数。使用新的JavaScript功能允许我们将其重构为

export const clean = () => del(['dist']);

现在,运行gulp clean时,应该删除该文件夹。接下来,我们需要做的是删除dist文件夹,运行图像、复制和样式任务,最后在每次开始开发时监视更改。这可以通过运行gulp cleangulp imagesgulp stylesgulp copy,然后gulp watch来完成。但是,当然,我们不会手动执行此操作。Gulp 有几个函数可以帮助我们组合任务。因此,让我们从 Gulp 中导入这些函数

import { src, dest, watch, series, parallel } from 'gulp';

series()将以一些任务作为参数,并按顺序(一个接一个)运行它们。而parallel()将以任务作为参数,并同时运行它们。让我们通过组合我们已经创建的任务来创建两个新的任务

export const dev = series(clean, parallel(styles, images, copy), watchForChanges)
export const build = series(clean, parallel(styles, images, copy))
export default dev;

这两个任务将执行完全相同的事情:清理dist文件夹,然后样式、图像和复制将在清理完成后并行运行。我们将在这些并行任务之后开始监视开发(开发的缩写)任务的更改。此外,我们还将dev导出为默认任务。

请注意,当我们运行构建任务时,我们希望我们的文件被最小化,图像被压缩,等等。因此,当我们运行此命令时,我们将不得不添加--prod标志。由于在运行构建任务时很容易忘记这一点,我们可以使用npm脚本为dev和build命令创建别名。让我们转到package.json,在脚本字段中,我们可能会发现类似于以下内容

"scripts": {
  "test": "echo "Error: no test specified" && exit 1"
}

让我们将其更改为以下内容

"scripts": {
  "start": "gulp",
  "build": "gulp build --prod"
},

这将允许我们在命令行中运行npm run start,它将转到脚本字段并找到与start对应的命令。在我们的例子中,start将运行gulp,而gulp将运行默认的gulp任务,即dev。同样,npm run build将运行gulp build --prod。这样,我们就可以完全忘记--prod标志,也可以忘记使用gulp命令运行Gulp任务。当然,我们的dev和build命令稍后会做更多的事情,但现在,我们有了在整个任务过程中将要使用的基础。

创建脚本任务

如前所述,为了捆绑我们的JavaScript文件,我们将需要一个模块捆绑器。webpack是目前最著名的选项,但它不是一个Gulp插件。相反,它是一个独立的插件,具有完全独立的设置和配置文件。幸运的是,有一个名为webpack-stream的软件包可以帮助我们在Gulp任务中使用webpack。因此,让我们安装这个软件包

npm install --save-dev webpack-stream

webpack 使用名为loaders的东西。加载器负责在webpack中转换文件。为了将新的Javascript版本转换为ES5,我们将需要一个名为babel-loader的加载器。我们还需要@babel/preset-env,但我们之前已经安装了它

npm install --save-dev babel-loader

让我们在Gulpfile的顶部导入webpack-stream

import webpack from 'webpack-stream';

此外,为了测试我们的任务,让我们在src/js/bundle.jssrc/js/components/slider.js中添加以下行

// bundle.js
import './components/slider';
console.log('bundle');


// slider.js
console.log('slider')

我们的脚本任务最终将如下所示

export const scripts = () => {
  return src('src/js/bundle.js')
  .pipe(webpack({
    module: {
      rules: [
        {
          test: /\.js$/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: []
            }
          }
        }
      ]
    },
    mode: PRODUCTION ? 'production' : 'development',
    devtool: !PRODUCTION ? 'inline-source-map' : false,
    output: {
      filename: 'bundle.js'
    },
  }))
  .pipe(dest('dist/js'));
}

让我们分解一下

  • 首先,我们在src()函数中指定bundle.js作为我们的入口点。
  • 然后,我们对webpack插件进行管道操作,并为它指定一些选项。
  • module选项中的rules字段让webpack知道要使用哪些加载器来转换我们的文件。在我们的例子中,我们需要使用babel-loader来转换JavaScript文件。
  • mode选项是生产或开发。对于开发,webpack不会最小化输出的JavaScript包,但对于生产,它会最小化。因此,我们不需要单独的Gulp插件来最小化JavaScript,因为webpack可以根据我们的PRODUCTION常量来完成此操作。
  • devtool选项将添加源映射,但在生产环境中不会添加。但在开发中,我们将使用inline-source-maps。这种源映射是最准确的,尽管创建它可能有点慢。如果您发现它太慢,请查看其他选项here。它们不会像inline-source-maps那样准确,但它们可以非常快。
  • 最后,output选项可以指定有关输出文件的一些信息。在我们的例子中,我们只需要更改文件名。如果我们没有指定文件名,webpack将生成一个以哈希值为文件名的包。阅读有关这些选项的更多信息here.

现在,我们应该能够运行gulp scriptsgulp scripts --prod,并在dist/js中看到一个bundle.js文件。确保最小化和源映射正常工作。现在,让我们在WordPress中排队我们的JavaScript文件,这可能在主题的functions.php文件中,或者您编写函数的任何地方。

<?php
function _themename_assets() {
  wp_enqueue_style( '_themename-stylesheet', get_template_directory_uri() . '/dist/css/bundle.css', array(), '1.0.0', 'all' );
  
  wp_enqueue_script( '_themename-scripts', get_template_directory_uri() . '/dist/js/bundle.js', array(), '1.0.0', true );
}
add_action('wp_enqueue_scripts', '_themename_assets');

现在,查看控制台,让我们通过检查控制台日志来自的文件来确认源映射是否正常工作

如果没有源映射,两个日志都将显示来自bundle.js

如果我们想像我们对样式那样创建多个JavaScript包怎么办?让我们在src/js中创建一个名为admin.js的文件。您可能会认为我们可以简单地将src()中的入口点更改为数组,如下所示

export const scripts = () => {
  return src(['src/js/bundle.js','src/js/admin.js'])
  .
  .
}

但是,这不会工作。webpack的工作方式与普通Gulp插件略有不同。我们上面所做的仍然会在dist文件夹中创建一个名为bundle.js的文件。webpack-stream提供了几个创建多个入口点的解决方案。我选择使用第二个解决方案,因为它将允许我们通过将数组传递给src()来创建多个包,就像我们对样式所做的那样。这将要求我们安装vinyl-named

npm install --save-dev vinyl-named

导入它

import named from 'vinyl-named';

…然后更新脚本任务

export const scripts = () => {
  return src(['src/js/bundle.js','src/js/admin.js'])
  .pipe(named())
  .pipe(webpack({
    module: {
      rules: [
        {
          test: /\.js$/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env']
            }
          }
        }
      ]
    },
    mode: PRODUCTION ? 'production' : 'development',
    devtool: !PRODUCTION ? 'inline-source-map' : false,
    output: {
      filename: '[name].js'
    },
  }))
  .pipe(dest('dist/js'));
}

唯一的区别是,我们现在在src()中有一个数组。然后我们在webpack之前对named插件进行管道操作,这允许我们使用[name]占位符在output字段的filename中,而不是直接硬编码文件名。运行任务后,我们在dist/js中得到两个包。

webpack提供的另一个功能是使用来自外部来源的库,而不是将它们捆绑到最终包中。例如,假设您的包需要使用jQuery。您可以运行npm install jquery --save,然后将其导入到您的包中import $ from 'jquery'。但是,这将增加包的大小,在某些情况下,您可能已经通过CDN加载了jQuery,或者——在WordPress的情况下——它可以作为依赖项存在,如下所示

wp_enqueue_script( '_themename-scripts', get_template_directory_uri() . '/dist/js/bundle.js', array('jquery'), '1.0.0', true );

现在,WordPress 将使用普通的脚本标签来加载 jQuery。那么,如何在我们的捆绑包中使用 `import $ from` `'jquery'` 呢?答案是使用 webpack 的 externals 选项。让我们修改我们的脚本任务,将它添加进来。

export const scripts = () => {
  return src(['src/js/bundle.js','src/js/admin.js'])
    .pipe(named())
    .pipe(webpack({
      module: {
        rules: [
          {
            test: /\.js$/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: []
            }
          }
        }
      ]
    },
    mode: PRODUCTION ? 'production' : 'development',
    devtool: !PRODUCTION ? 'inline-source-map' : false,
    output: {
      filename: '[name].js'
    },
    externals: {
      jquery: 'jQuery'
    },
  }))
  .pipe(dest('dist/js'));
}

在 externals 选项中,`jquery` 是一个键,用于标识我们要导入的库的名称。在本例中,它将是 `import $ from` `'jquery'`。而值 `jQuery` 是一个全局变量的名称,该变量存储着该库。现在尝试在捆绑包中使用 `import $ from ‘jquery’`,并使用 `$` 来使用 jQuery — 它应该可以完美地工作。

让我们也监控 JavaScript 文件的变化。

export const watchForChanges = () => {
  watch('src/scss/**/*.scss', styles);
  watch('src/images/**/*.{jpg,jpeg,png,svg,gif}', images);
  watch(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'], copy);
  watch('src/js/**/*.js', scripts);
}

最后,将我们的脚本任务添加到 dev 和 build 任务中。

export const dev = series(clean, parallel(styles, images, copy, scripts), watchForChanges);
export const build = series(clean, parallel(styles, images, copy, scripts));

使用 Browsersync 刷新浏览器

现在,让我们通过安装 Browsersync 来改进我们的 watch 任务,Browsersync 是一个插件,它会在每次任务运行完成时刷新浏览器。

npm install browser-sync gulp --save-dev

像往常一样,让我们导入它。

import browserSync from "browser-sync";

接下来,我们将初始化一个 Browsersync 服务器,并编写两个新的任务。

const server = browserSync.create();
export const serve = done => {
  server.init({
    proxy: "http://localhost/yourFolderName" // put your local website link here
  });
  done();
};
export const reload = done => {
  server.reload();
  done();
};

为了使用 Browsersync 来控制浏览器,我们必须初始化一个 Browsersync 服务器。这与 WordPress 通常所在的本地服务器不同。第一个任务是 `serve`,它启动 Browsersync 服务器,并使用 `proxy` 选项指向我们的本地 WordPress 服务器。第二个任务只是简单地重新加载浏览器。

现在,我们需要在开发主题时运行这个服务器。我们可以将 serve 任务添加到 dev 系列任务中。

export const dev = series(clean, parallel(styles, images, copy, scripts), serve, watchForChanges);

现在运行 `npm start`,浏览器应该会打开一个与原始 URL 不同的新 URL。这个 URL 是 Browsersync 将要刷新的 URL。现在让我们使用 reload 任务,在任务完成后重新加载浏览器。

export const watchForChanges = () => {
  watch('src/scss/**/*.scss', series(styles, reload));
  watch('src/images/**/*.{jpg,jpeg,png,svg,gif}', series(images, reload));
  watch(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'], series(copy, reload));
  watch('src/js/**/*.js', series(scripts, reload));
  watch("**/*.php", reload);
}

如您所见,我们添加了一行,以便每次 PHP 文件发生变化时运行 reload 任务。我们还使用 `series()` 来等待我们的样式、图像、脚本和复制任务完成,然后再重新加载浏览器。现在,运行 `npm start`,并更改 Sass 文件中的内容。浏览器应该自动重新加载,并且更改会在任务运行完成后刷新后反映出来。

刷新后看不到 CSS 或 JavaScript 的更改?请确保在浏览器的检查器中禁用了缓存。

我们可以对样式任务做进一步的改进。Browsersync 允许我们将 CSS 直接注入页面,而无需重新加载浏览器。这可以通过在样式任务的末尾添加 `server.stream()` 来实现。

export const styles = () => {
  return src(['src/scss/bundle.scss', 'src/scss/admin.scss'])
    .pipe(gulpif(!PRODUCTION, sourcemaps.init()))
    .pipe(sass().on('error', sass.logError))
    .pipe(gulpif(PRODUCTION, postcss([ autoprefixer ])))
    .pipe(gulpif(PRODUCTION, cleanCss({compatibility:'ie8'})))
    .pipe(gulpif(!PRODUCTION, sourcemaps.write()))
    .pipe(dest('dist/css'))
    .pipe(server.stream());
}

现在,在 `watchForChanges` 任务中,我们不再需要为样式任务重新加载,所以让我们从它中删除 reload 任务。

export const watchForChanges = () => {
  watch('src/scss/**/*.scss', styles);
  .
  .
}

确保在 `watchForChanges` 已经运行的情况下停止它,然后再次运行它。尝试修改 `scss` 文件夹中的任何文件,更改应该会立即显示在浏览器中,甚至不需要重新加载。

将主题打包到 ZIP 文件中

WordPress 主题通常被打包成一个 ZIP 文件,可以直接在 WordPress 管理界面中安装。我们可以创建一个任务,将所需的主题文件打包成 ZIP 文件。为此,我们需要安装另一个 Gulp 插件:gulp-zip

npm install --save-dev gulp-zip

并且,像往常一样,在顶部导入它。

import zip from "gulp-zip";

让我们也导入 `package.json` 文件中的 JSON 对象。我们需要它来获取包的名称,也就是我们主题的名称。

import info from "./package.json";

现在,让我们编写我们的任务。

export const compress = () => {
return src([
  "**/*",
  "!node_modules{,/**}",
  "!bundled{,/**}",
  "!src{,/**}",
  "!.babelrc",
  "!.gitignore",
  "!gulpfile.babel.js",
  "!package.json",
  "!package-lock.json",
  ])
  .pipe(zip(`${info.name}.zip`))
  .pipe(dest('bundled'));
};

我们将通过 `src()` 传递我们需要压缩的文件和文件夹,这些文件和文件夹基本上是所有文件和文件夹 ( `**/` ),除了少数特定的文件类型,这些文件类型前面带有 `!`。接下来,我们将管道连接到 gulp-zip 插件,并将文件命名为 `package.json` 文件中的主题名称 (info.name)。结果是在一个名为 `bundled` 的新文件夹中生成一个新的 ZIP 文件。

尝试运行 `gulp compress`,并确保一切正常。打开生成的 ZIP 文件,确保它只包含运行主题所需的那些文件和文件夹。

通常情况下,我们只需要在主题文件构建完成后才打包成 ZIP 文件。所以让我们将 compress 任务添加到 build 任务中,以便它只在我们需要的时候运行。

export const build = series(clean, parallel(styles, images, copy, scripts), compress);

现在运行 `npm run build` 应该会以生产模式运行我们所有的任务。

替换 ZIP 文件中的占位符前缀

在打包文件之前,我们需要做的一步是扫描它们,并将 `themename` 占位符替换为我们要使用的主题名称。你可能已经猜到了,确实有一个 Gulp 插件可以为我们做到这一点,叫做 gulp-replace

npm install --save-dev gulp-replace

然后导入它。

import replace from "gulp-replace";

我们希望这个任务在我们的文件打包成 ZIP 文件之前立即运行,所以让我们通过将其插入正确的位置来修改 compress 任务。

export const compress = () => {
return src([
    "**/*",
    "!node_modules{,/**}",
    "!bundled{,/**}",
    "!src{,/**}",
    "!.babelrc",
    "!.gitignore",
    "!gulpfile.babel.js",
    "!package.json",
    "!package-lock.json",
  ])
  .pipe(replace("_themename", info.name))
  .pipe(zip(`${info.name}.zip`))
  .pipe(dest('bundled'));
};

现在尝试使用 `npm run build` 构建主题,然后解压缩 `bundled` 文件夹中的文件。打开任何可能使用过 `_themename` 占位符的 PHP 文件,并确保它被实际的主题名称替换。

我在使用 replace 插件时发现了一个需要注意的陷阱。如果主题中包含 ZIP 文件(例如,你在主题中打包了 WordPress 插件),那么当它们通过 replace 插件时,它们会损坏。这可以通过使用 `gulp-if` 语句来忽略 ZIP 文件来解决。

.pipe(
  gulpif(
    file => file.relative.split(".").pop() !== "zip",
    replace("_themename", info.name)
  )
)

生成 POT 文件

翻译在 WordPress 社区中很重要,所以对于我们的最后一个任务,让我们扫描所有的 PHP 文件,并生成一个 POT 文件,该文件用于翻译。幸运的是,我们也有一个 gulp 插件可以做到这一点

npm install --save-dev gulp-wp-pot

当然,导入它。

import wpPot from "gulp-wp-pot";

这是我们最后的任务。

export const pot = () => {
  return src("**/*.php")
  .pipe(
      wpPot({
        domain: "_themename",
        package: info.name
      })
    )
  .pipe(dest(`languages/${info.name}.pot`));
};

我们希望每次构建主题时都生成 POT 文件。

export const build = series(clean, parallel(styles, images, copy, scripts), pot, compress);

总结

这是完整的 Gulpfile,包括本文中介绍的所有任务。

import { src, dest, watch, series, parallel } from 'gulp';
import yargs from 'yargs';
import sass from 'gulp-sass';
import cleanCss from 'gulp-clean-css';
import gulpif from 'gulp-if';
import postcss from 'gulp-postcss';
import sourcemaps from 'gulp-sourcemaps';
import autoprefixer from 'autoprefixer';
import imagemin from 'gulp-imagemin';
import del from 'del';
import webpack from 'webpack-stream';
import named from 'vinyl-named';
import browserSync from "browser-sync";
import zip from "gulp-zip";
import info from "./package.json";
import replace from "gulp-replace";
import wpPot from "gulp-wp-pot";
  const PRODUCTION = yargs.argv.prod;
  const server = browserSync.create();
  export const serve = done => {
    server.init({
      proxy: "http://localhost:8888/starter"
    });
    done();
  };
  export const reload = done => {
    server.reload();
    done();
  };
  export const clean = () => del(['dist']);
    
  export const styles = () => {
  return src(['src/scss/bundle.scss', 'src/scss/admin.scss'])
    .pipe(gulpif(!PRODUCTION, sourcemaps.init()))
    .pipe(sass().on('error', sass.logError))
    .pipe(gulpif(PRODUCTION, postcss([ autoprefixer ])))
    .pipe(gulpif(PRODUCTION, cleanCss({compatibility:'ie8'})))
    .pipe(gulpif(!PRODUCTION, sourcemaps.write()))
    .pipe(dest('dist/css'))
    .pipe(server.stream());
  }
  export const images = () => {
  return src('src/images/**/*.{jpg,jpeg,png,svg,gif}')
    .pipe(gulpif(PRODUCTION, imagemin()))
    .pipe(dest('dist/images'));
  }
  export const copy = () => {
    return src(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'])
    .pipe(dest('dist'));
  }
    export const scripts = () => {
      return src(['src/js/bundle.js','src/js/admin.js'])
      .pipe(named())
      .pipe(webpack({
        module: {
        rules: [
          {
            test: /\.js$/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: []
                }
              }
            }
          ]
        },
        mode: PRODUCTION ? 'production' : 'development',
        devtool: !PRODUCTION ? 'inline-source-map' : false,
        output: {
          filename: '[name].js'
        },
        externals: {
          jquery: 'jQuery'
        },
      }))
      .pipe(dest('dist/js'));
    }
    export const compress = () => {
      return src([
        "**/*",
        "!node_modules{,/**}",
        "!bundled{,/**}",
        "!src{,/**}",
        "!.babelrc",
        "!.gitignore",
        "!gulpfile.babel.js",
        "!package.json",
        "!package-lock.json",
      ])
      .pipe(
        gulpif(
          file => file.relative.split(".").pop() !== "zip",
          replace("_themename", info.name)
        )
      )
      .pipe(zip(`${info.name}.zip`))
      .pipe(dest('bundled'));
    };
    export const pot = () => {
      return src("**/*.php")
        .pipe(
          wpPot({
            domain: "_themename",
            package: info.name
          })
        )
      .pipe(dest(`languages/${info.name}.pot`));
    };
    export const watchForChanges = () => {
      watch('src/scss/**/*.scss', styles);
      watch('src/images/**/*.{jpg,jpeg,png,svg,gif}', series(images, reload));
      watch(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'], series(copy, reload));
      watch('src/js/**/*.js', series(scripts, reload));
      watch("**/*.php", reload);
    } 
    export const dev = series(clean, parallel(styles, images, copy, scripts), serve, watchForChanges);
    export const build = series(clean, parallel(styles, images, copy, scripts), pot, compress);
    export default dev;

呼,这些都是!我希望您从本系列文章中有所收获,并希望它能帮助您简化 WordPress 的开发流程。如果您有任何问题,请在评论中告诉我。如果您对完整的 WordPress 主题开发课程感兴趣,请务必查看我的 Udemy 课程,为您提供特别优惠。😀