UNPKG

makestatic-pack-webpack

Version:

Bundle assets using webpack

258 lines (215 loc) 7.79 kB
const SingleEntryDependency = require('webpack/lib/dependencies/SingleEntryDependency') const MultiEntryDependency = require('webpack/lib/dependencies/MultiEntryDependency') const path = require('path') const when = require('when') const node = require('when/node') /** * System webpack plugin. * * @class PackPlugin */ class PackPlugin { /** * Create a PackPlugin. * * @param {Object} context the processing context. * @param {Boolean} watching whether the source files are being watched. */ constructor (context = {}, watching = false) { this.name = 'PackPlugin' this.context = context this.watching = watching } /** * Adds compilation entries. * * @function addFiles * @member PackPlugin * @param {Object} compilation the webpack compilation. * @param {Array} files list of files to add to the compilation. * * @returns a promise that resolves when all the files have been added. */ addFiles (compilation, files) { return when.all(Array.prototype.concat(files).map((file) => { // do not add javascript files other than the entry points // allows treating some javascript files as static assets // eg: when you just want to include a library // and load in the browser, place the files in a `vendor` directory if (!/vendor\//.test(file.name) && /\.jsx?$/.test(file.name)) { return when.resolve() } // manifest files are dynamic, don't add them to the compilation /* istanbul ignore next: only applies to repeat builds in watch mode */ /* if (this.context.options.manifest && file.path === this.context.options.manifest) { return when.resolve() } */ // source map files are also dynamic /* istanbul ignore next: only applies to repeat builds in watch mode */ if (/\.map$/.test(file.name)) { return when.resolve() } let rel = file.name if (path.isAbsolute(rel)) { rel = file.relative(this.context.config.context) } else { let ptn = new RegExp('^' + this.context.config.context) // the webpack context is set so if the input file path // is relative to the current working directory we need // to strip the context path otherwise webpack is very unhappy /* istanbul ignore else: * * not going to mock relative path outside context * */ if (ptn.test(rel)) { rel = rel.replace(ptn, '').replace(/^\/+/, '') } } let dep = new MultiEntryDependency( [new SingleEntryDependency(`./${rel}`)], rel) const addEntryFn = compilation.addEntry.bind(compilation) return node.call(addEntryFn, this.context.config.context, dep, rel) })) } /** * Webpack plugin entry point that configures the webpack plugin functions. * * @function apply * @member PackPlugin * @param {Object} compiler the webpack compiler. */ apply (compiler) { const log = this.context.log let transient = [] let files = [] // all assets including transient ones let all = {} compiler.plugin('invalid', (fileName, changeTime) => { /* istanbul ignore next: not going to mock log message */ log.info('[invalidated] %s %s', fileName, changeTime) }) // inject files into webpack's pipeline compiler.plugin('make', (compilation, done) => { return this.addFiles(compilation, this.context.files) .done(() => done()) }) // get the assets and mutate file list compiler.plugin('after-compile', (compilation, done) => { // clear file list otherwise we duplicate in watch mode files = [] const assets = Object.keys(compilation.assets) assets.forEach((name) => { let relativeSource = path.join(this.context.options.input, name) let absoluteSource = path.join(this.context.options.root, name) let relativeOutput = name let file const dep = compilation.modules.find((el) => { if (el.userRequest === absoluteSource) { return el } }) if (dep && dep.error) { return done(dep.error) // transformed entry points do not find as dependencies // as they have been renamed so the compilation modules find fails // so we create a file for the transformed output } else if (!dep) { const asset = compilation.assets[name] file = this.context.getFile( {output: name, content: asset.source(), name: name}) } else { // get the compiled source content for the file // either from the raw source loader buffer or from // webpack's compiled value /* istanbul ignore next: */ const content = dep._src ? dep._src : dep._source._value // find the file definition for the asset file = this.context.files.find((file) => { let relative = file.relative(this.context.config.context) if (name === relative) { return file } }) // remove existing asset, might well be renamed (file extension) delete compilation.assets[name] // update output path, will set the extension if necessary file.output = relativeOutput // TODO: only invalidate files that have changed // invalidate file if (this.watching) { file.emitted = false file.existsAt = null } // update content for the file file.content = content } // NOTE: must use `file.output` so the file is renamed before output all[file.output] = file if (!file.transient) { // add to the list of files being processed files.push(file) } }) done() }) compiler.plugin('emit', (compilation, done) => { // don't optimize if the compilation has errors if (compilation.errors.length) { console.dir(compilation.errors) return done() } // assign asset list so that future plugins can access assets by key // including transient assets so that plugins that require transient // assets will be able to access them with watch mode enabled this.context.assets = all // console.dir(this.context.list.get('csp.txt')) // run the optimization lifecycle const lifecycle = this.context.lifecycle const phases = lifecycle.main //console.log('starting compile') lifecycle.run(phases) .then(() => { // remove transient files from compilation assets - this allows // plugins to mark files as transient during the main lifecycle for (let k in this.context.assets) { if (this.context.assets[k].transient) { transient.push(this.context.assets[k]) this.context.list.remove(this.context.assets[k]) } } compilation.assets = this.context.assets done() }) .catch(done) }) compiler.plugin('after-emit', (compilation, done) => { // need to add transient files back when watching if (this.watching) { transient.forEach((file) => { this.context.list.add(file) }) } // not performing a deployment if (!this.context.options.provider) { return done() } const lifecycle = this.context.lifecycle const phases = lifecycle.deploy lifecycle.run(phases) .then(() => { done() }) .catch(done) }) } } module.exports = PackPlugin