这是关于为 WordPress 主题开发创建 Gulp 工作流程的两个部分系列中的第二篇文章。 第一部分重点介绍了在 WordPress 主题项目中 Gulp 的初始安装、设置和组织。 本文深入探讨了 Gulp 将运行的任务,通过分解每个任务的功能以及如何定制它们来简化主题开发。
现在我们已经完成了 本系列的第一部分,使用安装了 Gulp 的 WordPress 主题项目进行设置,是时候深入研究我们希望它在开发主题时为我们执行的任务了。 我们将在本文中亲自动手,准备好编写一些代码!
文章系列
- 初始设置
- 创建任务 (本文)
创建样式任务
让我们从将 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.php
和 footer.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.scss
和 slider.scss
)。 因此,在我们开始编写代码之前,让我们将此添加到我们的改进愿望清单中。
我们还需要做的另一件事是为我们处理供应商前缀。 没有什么比必须自己编写和管理所有这些前缀更糟糕的了,而 Autoprefixer 是可以为我们执行此操作的工具。
并且,为了让 Autoprefixer 发挥作用,我们需要 PostCSS 插件。
好的,总共增加了三个插件和任务,我们需要运行。 让我们安装所有三个插件
npm install --save-dev gulp-sourcemaps gulp-postcss autoprefixer
所以 gulp-sourcemaps 显然将用于源地图。 gulp-postcss 和 autoprefixer 将用于将自动前缀添加到我们的 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 插件,我们必须遵循一些步骤
- 首先,我们使用
sourcemaps.init()
初始化插件。 - 接下来,将您想要映射的所有插件都通过管道传递。
- 最后,在将 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.css
和 admin.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匹配src
(src/**/*
)中的所有文件和文件夹,除了图像、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 clean
、gulp images
、gulp styles
、gulp 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.js
和src/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 scripts
和gulp 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 课程,为您提供特别优惠。😀
嗨,这非常有用,您在 GitHub 上有这些文件吗?
你好,你可以在我课程仓库的这个分支中找到大部分内容:https://github.com/alialaa/firsttheme-steps/tree/creating-replaceable-placeholder-prefix
但是我真的建议您按照文章操作,自己动手输入。祝你好运! :)
`functions.php` 文件中存在一些错误。此外,如果没有一个适当的 `index.php` 或 `styles.css` 文件来演示,这将无法工作。第一个问题是,源代码中包含 `assets`,应该改为 `'/dist/js/bundle.js'`,还缺少一个闭合的 `?>`。
是的,我在第一篇文章中提到过,我假设您已经拥有一个带有必要文件的 WordPress 主题(index.php、styles.css)。感谢您发现这个错误,我已经删除了 `'/assets'`,但是在这种情况下,闭合标签是不必要的。
在我继续学习的过程中,我认为在你开始讨论将 JavaScript 分成多个部分后,你所介绍的步骤就失败了,因为你引入了一个不存在的 `admin.js` 文件。你应该更改它,以便 `slider.js` 和 `bundle.js` 不合并。另外,如果有几个 JavaScript 文件,`functions.php` 需要更改吗?是否需要包含单独的 JavaScript 文件?
admin.js 的想法是,如果你需要创建另一个 js 包(除了 bundle.js),在这种情况下,我们将有两个 js 包,因此你必须修改 functions.php 来排队第二个文件。在这种情况下,仅为 wp-admin 面板排队 admin.js。
非常棒且深入的文章。
我个人会改变的一件事是,我会将主题的根文件夹保存在仓库的子文件夹中,从而将主题和 npm 包根文件夹分开。这样做的主要(虽然不是唯一的)原因是 Mac OS 和 Windows 上基于 docker 的环境的性能,这些环境非常非常非常不喜欢将像 node modules 这样的东西作为卷挂载。如此多的文件会降低此类卷的读取操作速度,即使使用 :cached 模式也是如此。
我不确定你是否完成了这篇文章,但实际上,在最后我们只使用必要的文件和文件夹为主题创建一个 ZIP 文件;忽略所有仅在开发中使用的文件和文件夹,例如 node_modules 等。我不确定,但我认为这将解决你的问题。
喜欢这个深入的教程,谢谢 Ali!我确保完全复制了 autoprefixer 管道,但 autoprefixer 对我来说不起作用。到目前为止,其他一切都很好(我刚刚完成了添加 Watch 任务)。你知道为什么 autoprefixer 可能不起作用吗?我尝试添加了一个 display:grid; 样式规则以及一个过渡样式规则,但当我查看 DevTools 时,没有出现 -moz 或 -webkit 等。
嗨 Zach,如果你不介意,你可以实际将你的主题文件夹发送给我,我可以看看。我的电子邮件是 [email protected] :)
嗨!
这里也有同样的问题 ;) 我尝试在 autoprefixer 后添加方括号:.pipe(gulpif(PRODUCTION, postcss([ autoprefixer() ]))),因为我在 post-css 文档中看到了它,但它也不起作用……
你还记得 Zack Miller 的问题是什么吗?
顺便说一句,我通过这篇文章学到了很多关于 Gulp 的知识,非常非常感谢!
你好 Nicolas,你可能需要告诉 autoprefixer 你想支持旧浏览器。试试这个
.pipe(gulpif(PRODUCTION, postcss([ autoprefixer({ grid: true, browsers: [‘last 2 versions’, ‘ie 6-8’, ‘Firefox > 20’] }) ])))
非常感谢 Ali,它有效!
我甚至尝试通过告诉 Autoprefixer 在我的 package.json 文件中支持旧浏览器来更进一步,它也起作用了……
再次感谢你的回答和帮助,也感谢这篇文章;)
谢谢 Ali!我给你发了一封电子邮件,里面有我的主题文件夹。
多么恰到好处的文章!!
虽然有一些由鼓舞人心的人创建的优秀选项,我们可以简单地下载并激活它们,但根据我的经验,除非你完全理解这些选项是如何组合在一起的,否则当你遇到某些东西无法按预期工作时,你可能会感到沮丧。我认为你必须(通过研究)创建自己的流程来真正理解这一点。
我希望使用 Gulp 和 Webpack 的组合。很多关于 Gulp 的文档都是 v3 版本,在为 WordPress 开发时,单独使用 Webpack 会有缺点。你的文章正是我想要的。
我在 Udemy 上购买了你的课程 :-)
我将在这个流程中添加一个补充,即 SVG 雪碧图。
一个问题:我们是否通常忽略标准的 WordPress style.css 文件?换句话说,只用它来实现所需的标题,但不要排队它?
你好 Damian,抱歉回复晚了。由于某种原因,我在垃圾邮件中收到了评论邮件。无论如何,非常感谢你的留言 :) 我会在有时间的时候尝试研究 SVG 的问题。至于你的问题,我个人保留 style.css 只是为了主题信息,并使用我自己的 CSS/SASS 文件夹组织,但当然,style.css 仍然可以使用,这只是我的偏好 :)
嗨,这很不错,但我对前缀更改大写单词有点困惑。请解释一下。
你好 Mominul,你指的是哪个大写单词?请澄清一下。
您好,感谢您提供这个很棒的教程!坦率地说,我没有真正遵循教程,我只是使用 yarn 安装了依赖项,复制了 gulp 配置,它就直接开箱即用,满足了我的需求——scss、es6 加载器、压缩、压缩、构建一个带有插件的 zip 文件。
我一直都在寻找这个,感谢您!
你很棒!
嗨!如果你在使用
npm install --save-dev gulp-sourcemaps gulp-postcss autoprefixer
时遇到问题,请尝试最后安装gulp-postcss
感谢您提供这篇很棒的文章。除了一个问题,其他一切运行良好。当我尝试回显 get_template_directory_uri() 时,browsersync 会从返回的 URL 中删除 http:。因此,我得到的是 //localhost:3000/my/theme/directories,而不是预期的 http://localhost:3000/my/theme/directories 您知道如何解决这个问题吗?
我怀疑这不是由 browsersync 引起的。如果你使用普通端口,它是否可以正常工作?
很棒的文章,我学到了很多东西。由于我主要制作插件,我正在对其进行调整以适应我的特定需求。只有一件事我想要做,但这里没有提到。我希望我的最终 CSS 文件具有 .min.css 扩展名。但是,该文件始终具有 .css 扩展名,而且我找不到如何像对 JS 文件一样将输出管道到更改文件名。你能解释一下该怎么做吗?
您好,您可以尝试在 dest 之前管道化 gulp rename:https://npmjs.net.cn/package/gulp-rename
非常感谢!:)
您好!
我尝试使用 jQuery 运行你的示例代码,不起作用。
我认为,当你将 jquery 添加到 php 函数中时,你必须编写类似下面的代码
wp_enqueue_script( ‘jquery’, ‘https://code.jqueryjs.cn/jquery-3.4.0.min.js’, array(‘jquery’), ‘1.0.0’, true);
而不是
wp_enqueue_script( ‘_themename-scripts’, get_template_directory_uri() . ‘/dist/js/bundle.js’, array(‘jquery’), ‘1.0.0’, true );
也许我不理解某些内容?)
究竟是什么不起作用?是否有任何特定的错误?
感谢您提供这个教程,它在帮助我理解 gulp 4 设置方面非常有用。
一个问题:当我需要使用 git 提交进行操作时,如何停止/暂停流程?例如,我按下 CTRL + C(Git Bash)来停止合并 git 分支,这最终会运行 watchforchanges 并创建一个新的 bundle.css 文件
我不明白,请解释一下?你停止流程来合并一个分支,然后你重新启动流程?这究竟有什么问题?
我使用这段代码创建了一个 1:1 安装,但由于某种原因,当我保存 bundle.scss 文件时,监视器没有响应。我需要修改全局 Node 或 NPM 配置才能让它“监听”吗?我知道我必须为 Webpack 添加额外的轮询,但在这里看不到任何关于此要求的参考……不过,感谢您提供这个很棒的教程!
您好,如果您遵循了教程,它应该可以正常工作。您可能输入了一些错误。如果您无法解决问题,可以将您的文件夹发送给我,我可以看看。
在检查了代码并确保一切都是最新的之后,我发现代码在 OSX 上运行良好,但在运行 Linux 的 Vagrant 机器上却不行。(https://varyingvagrantvagrants.org/) 解决方案是更改
到
太棒了。我正在开始制作我的第一个 WP 主题,并且想要使用 Gulp 设置我的环境。这正是我想要的东西。谢谢!