From 712e5996af3b00e76b1b70d23aee0c3c42d33455 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Sun, 18 Mar 2018 15:18:59 -0400 Subject: [PATCH] Package: Add `@wordpress/custom-templated-path-webpack-plugin` package --- .../README.md | 53 +++++++++++++++++++ .../package.json | 33 ++++++++++++ .../src/index.js | 52 ++++++++++++++++++ .../test/fixtures/.gitignore | 1 + .../test/fixtures/entry/index.js | 1 + .../test/fixtures/webpack.config.js | 39 ++++++++++++++ .../test/index.js | 34 ++++++++++++ 7 files changed, 213 insertions(+) create mode 100644 packages/custom-templated-path-webpack-plugin/README.md create mode 100644 packages/custom-templated-path-webpack-plugin/package.json create mode 100644 packages/custom-templated-path-webpack-plugin/src/index.js create mode 100644 packages/custom-templated-path-webpack-plugin/test/fixtures/.gitignore create mode 100644 packages/custom-templated-path-webpack-plugin/test/fixtures/entry/index.js create mode 100644 packages/custom-templated-path-webpack-plugin/test/fixtures/webpack.config.js create mode 100644 packages/custom-templated-path-webpack-plugin/test/index.js diff --git a/packages/custom-templated-path-webpack-plugin/README.md b/packages/custom-templated-path-webpack-plugin/README.md new file mode 100644 index 0000000..3566a5d --- /dev/null +++ b/packages/custom-templated-path-webpack-plugin/README.md @@ -0,0 +1,53 @@ +# Custom Templated Path Webpack Plugin + +Webpack plugin for creating custom path template tags. Extend the [default set of template tags](https://webpack.js.org/configuration/output/#output-filename) with your own custom behavior. Hooks into Webpack's compilation process to allow you to replace tags with a substitute value. + +**Note:** This plugin targets Webpack 4.0 and newer, and is not compatible with older versions. + +## Usage + +Construct an instance of `CustomTemplatedPathPlugin` in your Webpack configurations `plugins` entry, passing an object where keys correspond to the template tag name. The value for each key is a function passed the original intended path and data corresponding to the asset. + +The following example creates a new `basename` tag to substitute the basename of each entry file in the build output file. When compiled, the built file will be output as `build-entry.js`. + +```js +const { basename } = require( 'path' ); +const CustomTemplatedPathPlugin = require( '@wordpress/custom-templated-path-webpack-plugin' ); + +module.exports = { + // ... + + entry: './entry', + + output: { + filename: 'build-[basename].js', + }, + + plugins: [ + new CustomTemplatedPathPlugin( { + basename( path, data ) { + let rawRequest; + + const entryModule = get( data, [ 'chunk', 'entryModule' ], {} ); + switch ( entryModule.type ) { + case 'javascript/auto': + rawRequest = entryModule.rawRequest; + break; + + case 'javascript/esm': + rawRequest = entryModule.rootModule.rawRequest; + break; + } + + if ( rawRequest ) { + return basename( rawRequest ); + } + + return path; + }, + } ), + ], +}; +``` + +For more examples, refer to Webpack's own [`TemplatedPathPlugin.js`](https://github.com/webpack/webpack/blob/v4.1.1/lib/TemplatedPathPlugin.js), which implements the base set of template tags. diff --git a/packages/custom-templated-path-webpack-plugin/package.json b/packages/custom-templated-path-webpack-plugin/package.json new file mode 100644 index 0000000..f9c5b21 --- /dev/null +++ b/packages/custom-templated-path-webpack-plugin/package.json @@ -0,0 +1,33 @@ +{ + "name": "@wordpress/custom-templated-path-webpack-plugin", + "version": "1.0.0", + "description": "Webpack plugin for creating custom path template tags", + "author": "WordPress", + "license": "GPL-2.0-or-later", + "keywords": [ + "webpack", + "webpack-plugin" + ], + "homepage": "https://github.com/WordPress/packages/tree/master/packages/custom-templated-path-webpack-plugin/", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/packages.git" + }, + "bugs": { + "url": "https://github.com/WordPress/packages/issues" + }, + "main": "build/index.js", + "module": "build-module/index.js", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "devDependencies": { + "webpack": "^4.1.1" + }, + "peerDependencies": { + "webpack": "^4.0.0" + } +} diff --git a/packages/custom-templated-path-webpack-plugin/src/index.js b/packages/custom-templated-path-webpack-plugin/src/index.js new file mode 100644 index 0000000..5216fd8 --- /dev/null +++ b/packages/custom-templated-path-webpack-plugin/src/index.js @@ -0,0 +1,52 @@ +/** + * External dependencies + */ +const escapeStringRegexp = require( 'escape-string-regexp' ); + +/** + * Webpack plugin for handling specific template tags in Webpack configuration + * values like those supported in the base Webpack functionality (e.g. `name`). + * + * @see webpack.TemplatedPathPlugin + */ +class CustomTemplatedPathPlugin { + /** + * CustomTemplatedPathPlugin constructor. Initializes handlers as a tuple + * set of RegExp, handler, where the regular expression is used in matching + * a Webpack asset path. + * + * @param {Object.} handlers Object keyed by tag to match, + * with function value returning + * replacement string. + */ + constructor( handlers ) { + this.handlers = []; + + for ( const [ key, handler ] of Object.entries( handlers ) ) { + const regexp = new RegExp( `\\[${ escapeStringRegexp( key ) }\\]`, 'gi' ); + this.handlers.push( [ regexp, handler ] ); + } + } + + /** + * Webpack plugin application logic. + * + * @param {Object} compiler Webpack compiler + */ + apply( compiler ) { + compiler.hooks.compilation.tap( 'CustomTemplatedPathPlugin', ( compilation ) => { + compilation.mainTemplate.hooks.assetPath.tap( 'CustomTemplatedPathPlugin', ( path, data ) => { + for ( let i = 0; i < this.handlers.length; i++ ) { + const [ regexp, handler ] = this.handlers[ i ]; + if ( regexp.test( path ) ) { + return path.replace( regexp, handler( path, data ) ); + } + } + + return path; + } ); + } ); + } +} + +module.exports = CustomTemplatedPathPlugin; diff --git a/packages/custom-templated-path-webpack-plugin/test/fixtures/.gitignore b/packages/custom-templated-path-webpack-plugin/test/fixtures/.gitignore new file mode 100644 index 0000000..afea1ce --- /dev/null +++ b/packages/custom-templated-path-webpack-plugin/test/fixtures/.gitignore @@ -0,0 +1 @@ +entry.js diff --git a/packages/custom-templated-path-webpack-plugin/test/fixtures/entry/index.js b/packages/custom-templated-path-webpack-plugin/test/fixtures/entry/index.js new file mode 100644 index 0000000..b894a23 --- /dev/null +++ b/packages/custom-templated-path-webpack-plugin/test/fixtures/entry/index.js @@ -0,0 +1 @@ +module.exports = null; diff --git a/packages/custom-templated-path-webpack-plugin/test/fixtures/webpack.config.js b/packages/custom-templated-path-webpack-plugin/test/fixtures/webpack.config.js new file mode 100644 index 0000000..66b810b --- /dev/null +++ b/packages/custom-templated-path-webpack-plugin/test/fixtures/webpack.config.js @@ -0,0 +1,39 @@ +/** + * External dependencies + */ + +const { basename } = require( 'path' ); + +/** + * Internal dependencies + */ + +const CustomTemplatedPathPlugin = require( '../../' ); + +module.exports = { + mode: 'development', + context: __dirname, + entry: './entry', + output: { + filename: '[basename].js', + path: __dirname, + }, + plugins: [ + new CustomTemplatedPathPlugin( { + basename( path, data ) { + console.log(data) + + let rawRequest; + if ( data && data.chunk && data.chunk.entryModule ) { + rawRequest = data.chunk.entryModule.rawRequest; + } + + if ( rawRequest ) { + return basename( rawRequest ); + } + + return path; + }, + } ), + ], +}; diff --git a/packages/custom-templated-path-webpack-plugin/test/index.js b/packages/custom-templated-path-webpack-plugin/test/index.js new file mode 100644 index 0000000..613e238 --- /dev/null +++ b/packages/custom-templated-path-webpack-plugin/test/index.js @@ -0,0 +1,34 @@ +/** + * External dependencies + */ + +const path = require( 'path' ); +const { promisify } = require( 'util' ); +const webpack = promisify( require( 'webpack' ) ); +const fs = require( 'fs' ); +const access = promisify( fs.access ); +const unlink = promisify( fs.unlink ); + +/** + * Internal dependencies + */ + +const config = require( './fixtures/webpack.config.js' ); +const CustomTemplatedPathPlugin = require( '../' ); + +describe( 'CustomTemplatedPathPlugin', () => { + const outputFile = path.join( __dirname, '/fixtures/entry.js' ) + + beforeAll( async () => { + // Remove output file so as not to report false positive from previous + // test. Absorb error since the file may not exist (unlink will throw). + try { + await unlink( outputFile ); + } catch ( error ) {} + } ); + + it( 'should resolve with basename output', async () => { + const stats = await webpack( config ); + await access( outputFile ); + } ); +} );