Commit 260cc898 authored by Dan Allen's avatar Dan Allen

migrate build to Gulp 4

- upgrade to Gulp 4
- refactor tasks to use Gulp 4 task system
- switch from map-stream to through2.obj
- switch from fs to fs-extra
- fix Gulp prettier+eslint integration
- rename tasks
parent 336b32a7
......@@ -24,7 +24,7 @@ bundle-stable:
cache: *pull_cache
script:
- *run_yarn
- node_modules/.bin/gulp pack
- node_modules/.bin/gulp bundle
artifacts:
paths:
- build/ui-bundle.zip
......@@ -35,7 +35,7 @@ bundle-dev:
cache: *pull_cache
script:
- *run_yarn
- node_modules/.bin/gulp pack
- node_modules/.bin/gulp bundle
artifacts:
expire_in: 1 day # unless marked as keep from job page
paths:
......@@ -47,7 +47,7 @@ pages:
cache: *pull_cache
script:
- *run_yarn
- node_modules/.bin/gulp build:preview
- node_modules/.bin/gulp preview:build
# FIXME figure out a way to avoid copying these files to preview site
- rm -rf public/_/{helpers,layouts,partials}
artifacts:
......
{
"description": "Build tasks for the Antora default UI project",
"flags.tasksDepth": 0
"flags.tasksDepth": 1
}
'use strict'
const connect = require('gulp-connect')
const { parallel, series, tree } = require('gulp')
const camelcase = (name) => name.replace(/[-]./g, (m) => m.substr(1).toUpperCase())
const exportTasks = require('./tasks/lib/export-tasks')
const task = require('./tasks/lib/task')
const taskFns = require('require-directory')(module, './tasks', { recurse: false, rename: camelcase })
const path = require('path')
const gulp = require('gulp')
const build = require('./tasks/build')
const buildPreview = require('./tasks/build-preview')
const format = require('./tasks/format')
const lintCss = require('./tasks/lint-css')
const lintJs = require('./tasks/lint-js')
const pack = require('./tasks/pack')
const preview = require('./tasks/preview')
const bundleName = 'ui'
const buildDir = 'build'
......@@ -18,34 +13,102 @@ const previewSiteSrcDir = 'preview-site-src'
const previewSiteDestDir = 'public'
const srcDir = 'src'
const destDir = path.join(previewSiteDestDir, '_')
const { reload: livereload } = process.env.LIVERELOAD === 'true' ? require('gulp-connect') : {}
const jsFiles = ['gulpfile.js', 'tasks/**/*.js', path.join(srcDir, '{helpers,js}/**/*.js')]
const cssFileGlobs = path.join(srcDir, 'css/**/*.css')
const jsFileGlobs = ['gulpfile.js', 'tasks/**/*.js', path.join(srcDir, '{helpers,js}/**/*.js')]
gulp.task('lint:css', () => lintCss(`${srcDir}/css/**/*.css`))
gulp.task('lint:js', () => lintJs(jsFiles))
gulp.task('lint', ['lint:css', 'lint:js'])
const { remove, lintCss, lintJs, format, build, pack, previewPages, previewServe } = taskFns
gulp.task('format', () => format(jsFiles))
const cleanTask = task({
name: 'clean',
desc: 'Clean files and folders generated by build',
then: remove(['build', 'public']),
})
gulp.task('build', function () {
return build(srcDir, destDir, this.seq.slice(0).some((name) => ~name.indexOf('preview')))
const lintCssTask = task({
name: 'lint:css',
desc: 'Lint the CSS source files using stylelint (standard config)',
then: lintCss(cssFileGlobs),
})
gulp.task('build:preview', ['build'], () =>
buildPreview(srcDir, destDir, previewSiteSrcDir, previewSiteDestDir, connect.reload)
)
const lintJsTask = task({
name: 'lint:js',
desc: 'Lint the JavaScript source files using eslint (JavaScript Standard Style)',
then: lintJs(jsFileGlobs),
})
const lintTask = task({
name: 'lint',
desc: 'Lint the CSS and JavaScript source files',
then: parallel(lintCssTask, lintJsTask),
})
const formatTask = task({
name: 'format',
desc: 'Format the JavaScript source files using prettify (JavaScript Standard Style)',
then: format(jsFileGlobs),
})
const buildTask = task({
name: 'build',
desc: 'Build and stage the UI assets for bundling',
then: build(srcDir, destDir, tree().nodes.some((name) => ~name.indexOf('preview'))),
})
const bundleBuildTask = task({
name: 'bundle:build',
desc: 'Lint the source files and build and stage the UI assets for bundling',
then: series(cleanTask, lintTask, buildTask),
})
gulp.task('preview', ['build:preview'], () =>
preview(previewSiteDestDir, {
const bundlePackTask = task({
name: 'bundle:pack',
desc: 'Create a bundle of the staged UI assets for publishing',
then: pack(destDir, buildDir, bundleName),
})
const bundleTask = task({
name: 'bundle',
desc: 'Clean, lint, build, and bundle the UI for publishing',
then: series(bundleBuildTask, bundlePackTask),
})
const previewPagesTask = task({
name: 'preview:pages',
desc: 'Generate pages for the preview by applying the specified layout template',
then: previewPages(srcDir, destDir, previewSiteSrcDir, previewSiteDestDir, livereload),
})
const previewBuildTask = task({
name: 'preview:build',
desc: 'Process and stage the UI assets and generate pages for the preview',
then: parallel(buildTask, previewPagesTask),
})
const previewServeTask = task({
name: 'preview:serve',
desc: 'Launch server to preview UI',
then: previewServe(previewSiteDestDir, {
port: 5252,
livereload: process.env.LIVERELOAD === 'true',
watch: {
src: [srcDir, previewSiteSrcDir],
onChange: () => gulp.start('build:preview'),
},
})
)
livereload,
watch: { src: [srcDir, previewSiteSrcDir], onChange: previewBuildTask },
}),
})
gulp.task('pack', ['build', 'lint'], () => pack(destDir, buildDir, bundleName))
const previewTask = task({
name: 'preview',
desc: 'Generate a preview site and launch a server to view it',
then: series(previewBuildTask, previewServeTask),
})
gulp.task('default', ['build'])
module.exports = exportTasks(
task({ name: 'default', desc: `(${bundleTask.displayName})`, then: series(bundleTask) }),
cleanTask,
lintTask,
formatTask,
buildTask,
bundleTask,
previewTask,
previewBuildTask
)
......@@ -5,11 +5,10 @@ const browserify = require('browserify')
const buffer = require('vinyl-buffer')
const concat = require('gulp-concat')
const cssnano = require('cssnano')
const fs = require('fs')
const fs = require('fs-extra')
const imagemin = require('gulp-imagemin')
const map = require('map-stream')
const merge = require('merge-stream')
const mkdirp = require('mkdirp')
const { obj: map } = require('through2')
const path = require('path')
const postcss = require('gulp-postcss')
const postcssCalc = require('postcss-calc')
......@@ -19,7 +18,7 @@ const postcssVar = require('postcss-custom-properties')
const uglify = require('gulp-uglify')
const vfs = require('vinyl-fs')
module.exports = (src, dest, preview) => {
module.exports = (src, dest, preview) => () => {
const opts = { base: src, cwd: src }
const postcssPlugins = [
postcssImport(),
......@@ -31,13 +30,7 @@ module.exports = (src, dest, preview) => {
const abspath = path.resolve('node_modules', relpath)
const basename = path.basename(abspath)
const destpath = path.join(dest, 'font', basename)
if (!fs.existsSync(destpath)) {
const dirname = path.dirname(destpath)
if (!fs.existsSync(dirname)) {
mkdirp.sync(dirname)
}
fs.copyFileSync(abspath, destpath)
}
if (!fs.pathExists(destpath)) fs.copy(abspath, destpath)
return path.join('..', 'font', basename)
},
},
......@@ -48,34 +41,27 @@ module.exports = (src, dest, preview) => {
preview ? () => {} : cssnano({ preset: 'default' }),
]
return merge([
return merge(
vfs
.src('js/+([0-9])-*.js', opts)
.pipe(uglify())
.pipe(concat('js/site.js')),
vfs
.src('js/vendor/*.js', Object.assign({ read: false }, opts))
.pipe(
// see https://gulpjs.org/recipes/browserify-multiple-destination.html
map((file, next) => {
map((file, enc, next) => {
file.contents = browserify(file.relative, { basedir: src, detectGlobals: false }).bundle()
next(null, file)
})
)
.pipe(buffer())
.pipe(uglify()),
vfs.src('css/site.css', opts).pipe(postcss(postcssPlugins)),
vfs.src('font/*.woff*(2)', opts),
vfs.src('img/**/*.{jpg,ico,png,svg}', opts).pipe(imagemin()),
vfs.src('helpers/*.js', opts),
vfs.src('layouts/*.hbs', opts),
vfs.src('partials/*.hbs', opts),
]).pipe(vfs.dest(dest))
vfs.src('partials/*.hbs', opts)
).pipe(vfs.dest(dest))
}
'use strict'
const vfs = require('vinyl-fs')
const prettier = require('./lib/gulp-prettier-eslint')
const vfs = require('vinyl-fs')
module.exports = (files) =>
module.exports = (files) => () =>
vfs
.src(files)
.pipe(prettier())
......
'use strict'
module.exports = (...tasks) =>
tasks.reduce((acc, task) => (acc[task.displayName || task.name] = task) && acc, { default: tasks.shift() })
'use strict'
const { PluginError } = require('gulp-util')
const { obj: map } = require('through2')
const PluginError = require('plugin-error')
const prettierEslint = require('prettier-eslint')
const map = require('map-stream')
module.exports = () => {
const report = { changed: 0, unchanged: 0 }
return map(format).on('end', () => {
return map(format).on('finish', () => {
if (report.changed > 0) {
const changed = 'formatted '
.concat(report.changed)
......@@ -23,12 +23,12 @@ module.exports = () => {
}
})
function format (file, next) {
function format (file, enc, next) {
if (file.isNull()) return next()
if (file.isStream()) return next(new PluginError('gulp-prettier-eslint', 'Streaming not supported'))
const input = file.contents.toString()
const output = prettierEslint({ text: input })
const output = prettierEslint({ text: input, filePath: file.path })
if (input === output) {
report.unchanged += 1
......
'use strict'
const metadata = require('undertaker/lib/helpers/metadata')
module.exports = ({ name, desc, anon, then: fn }) => {
if (name) {
const displayName = fn.displayName
if (displayName === '<series>' || displayName === '<parallel>') {
metadata.get(fn).tree.label = `${displayName} ${name}`
}
fn.displayName = name
}
if (desc) fn.description = desc
return fn
}
'use strict'
const vfs = require('vinyl-fs')
const stylelint = require('gulp-stylelint')
const vfs = require('vinyl-fs')
module.exports = (files) =>
vfs.src(files).pipe(
stylelint({
reporters: [{ formatter: 'string', console: true }],
})
)
module.exports = (files) => () =>
vfs.src(files).pipe(stylelint({ reporters: [{ formatter: 'string', console: true }] }))
'use strict'
const vfs = require('vinyl-fs')
const eslint = require('gulp-eslint')
const vfs = require('vinyl-fs')
module.exports = (files) =>
module.exports = (files) => (done) =>
vfs
.src(files)
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError())
.on('error', done)
......@@ -3,7 +3,7 @@
const vfs = require('vinyl-fs')
const zip = require('gulp-vinyl-zip')
module.exports = (src, dest, bundleName) =>
module.exports = (src, dest, bundleName) => () =>
vfs
.src('**/*', { base: src, cwd: src })
.pipe(zip.zip(`${bundleName}-bundle.zip`))
......
'use strict'
const fs = require('fs')
const fs = require('fs-extra')
const handlebars = require('handlebars')
const map = require('map-stream')
const { obj: map } = require('through2')
const path = require('path')
const { promisify } = require('util')
const requireFromString = require('require-from-string')
const vfs = require('vinyl-fs')
const yaml = require('js-yaml')
module.exports = async (src, dest, siteSrc, siteDest, sink) => {
const [uiModel, layouts] = await Promise.all([
module.exports = (src, dest, siteSrc, siteDest, onComplete) => () =>
Promise.all([
loadSampleUiModel(siteSrc),
compileLayouts(src),
registerPartials(src),
registerHelpers(src),
])
const stream = vfs
.src('**/*.html', { base: siteSrc, cwd: siteSrc })
.pipe(
map((file, next) => {
const compiledLayout = layouts[file.stem === '404' ? '404.hbs' : 'default.hbs']
const siteRootPath = path.relative(path.dirname(file.path), path.resolve(siteSrc))
uiModel.env = process.env
uiModel.siteRootPath = siteRootPath
uiModel.siteRootUrl = path.join(siteRootPath, 'index.html')
uiModel.uiRootPath = path.join(siteRootPath, '_')
uiModel.page.contents = file.contents.toString().trim()
file.contents = Buffer.from(compiledLayout(uiModel))
next(null, file)
})
)
.pipe(vfs.dest(siteDest))
if (sink) stream.pipe(sink())
return stream
}
]).then(([uiModel, layouts]) =>
vfs
.src('**/*.html', { base: siteSrc, cwd: siteSrc })
.pipe(
map((file, enc, next) => {
const compiledLayout = layouts[file.stem === '404' ? '404.hbs' : 'default.hbs']
const siteRootPath = path.relative(path.dirname(file.path), path.resolve(siteSrc))
uiModel.env = process.env
uiModel.siteRootPath = siteRootPath
uiModel.siteRootUrl = path.join(siteRootPath, 'index.html')
uiModel.uiRootPath = path.join(siteRootPath, '_')
uiModel.page.contents = file.contents.toString().trim()
file.contents = Buffer.from(compiledLayout(uiModel))
next(null, file)
})
)
.pipe(vfs.dest(siteDest))
.pipe(onComplete ? onComplete() : map((file, enc, next) => next()))
)
function loadSampleUiModel (siteSrc) {
return promisify(fs.readFile)(path.join(siteSrc, 'ui-model.yml'), 'utf8').then((contents) => yaml.safeLoad(contents))
return fs.readFile(path.join(siteSrc, 'ui-model.yml'), 'utf8').then((contents) => yaml.safeLoad(contents))
}
function registerPartials (src) {
return new Promise((resolve, reject) => {
return new Promise((resolve, reject) =>
vfs
.src('partials/*.hbs', { base: src, cwd: src })
.pipe(
map((file, next) => {
map((file, enc, next) => {
handlebars.registerPartial(file.stem, file.contents.toString())
next(null, file)
next()
})
)
.on('error', reject)
.on('end', resolve)
})
.on('finish', resolve)
)
}
function registerHelpers (src) {
return new Promise((resolve, reject) => {
return new Promise((resolve, reject) =>
vfs
.src('helpers/*.js', { base: src, cwd: src })
.pipe(
map((file, next) => {
const helperFunction = requireFromString(file.contents.toString())
handlebars.registerHelper(file.stem, helperFunction)
next(null, file)
map((file, enc, next) => {
handlebars.registerHelper(file.stem, requireFromString(file.contents.toString()))
next()
})
)
.on('error', reject)
.on('end', resolve)
})
.on('finish', resolve)
)
}
function compileLayouts (src) {
const layouts = {}
return new Promise((resolve, reject) => {
return new Promise((resolve, reject) =>
vfs
.src('layouts/*.hbs', { base: src, cwd: src })
.pipe(
map((file, next) => {
map((file, enc, next) => {
layouts[file.basename] = handlebars.compile(file.contents.toString(), { preventIndent: true })
next(null, file)
next()
})
)
.on('error', reject)
.on('end', () => resolve(layouts))
})
.on('finish', () => resolve(layouts))
)
}
'use strict'
const connect = require('gulp-connect')
const chokidar = require('chokidar')
const connect = require('gulp-connect')
module.exports = (serveDir, opts) => {
let watch
if (opts) {
opts = Object.assign({}, opts)
watch = opts.watch
delete opts.watch
} else {
opts = {}
}
module.exports = (serveDir, opts = {}) => (done) => {
const watch = opts.watch
delete opts.watch
opts = Object.assign({ root: serveDir }, opts)
let onStart
if (watch && watch.src && watch.onChange) {
onStart = () => {
onStart = () =>
chokidar
.watch(watch.src, { ignoreInitial: true })
.on('add', watch.onChange)
.on('change', watch.onChange)
.on('unlink', watch.onChange)
}
}
opts.root = serveDir
connect.server(opts, onStart)
connect.server(opts, function () {
this.server.on('close', done)
if (onStart) onStart()
})
}
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment