aboutsummaryrefslogtreecommitdiffstats
path: root/build
diff options
context:
space:
mode:
authorTimmy Willison <timmywil@users.noreply.github.com>2023-09-18 12:39:00 -0400
committerGitHub <noreply@github.com>2023-09-18 12:39:00 -0400
commit2bdecf8b7bd10864e5337a4e24e39476c78cf23a (patch)
tree4685fc5ca912e368c294a3949c7ef5b663fec980 /build
parentf75daab09102a4dd5107deadb55d4a169f86254a (diff)
downloadjquery-2bdecf8b7bd10864e5337a4e24e39476c78cf23a.tar.gz
jquery-2bdecf8b7bd10864e5337a4e24e39476c78cf23a.zip
Build: migrate most grunt tasks off of grunt
Updated tasks include: - lint - npmcopy - build, minify, and process for distribution. - new custom build command using yargs - compare size of minified/gzip built files - pretest scripts, including qunit-fixture, babel transpilation, and npmcopy - node smoke tests - promises aplus tests - new watch task using `rollup.watch` directly Also: - upgraded husky and added the new lint command - updated lint config to use new "flat" config format. See https://eslint.org/docs/latest/use/configure/configuration-files-new - Temporarily disabled one lint rule until flat config is supported by eslint-plugin-import. See https://github.com/import-js/eslint-plugin-import/issues/2556 - committed package-lock.json - updated all test scripts to use the new build - added an express test server that uses middleware-mockserver (this can be used to run tests without karma) - build-all-variants is now build:all Close gh-5318
Diffstat (limited to 'build')
-rwxr-xr-xbuild/command.js75
-rw-r--r--build/grunt-tasks/testswarm.js (renamed from build/tasks/testswarm.js)0
-rw-r--r--build/tasks/build.js630
-rw-r--r--build/tasks/compare_size.mjs128
-rw-r--r--build/tasks/dist.js95
-rw-r--r--build/tasks/lib/getTimestamp.js9
-rw-r--r--build/tasks/lib/isCleanWorkingDir.js9
-rw-r--r--build/tasks/lib/slim-exclude.js (renamed from build/tasks/lib/slim-build-flags.js)10
-rw-r--r--build/tasks/lib/spawn_test.js16
-rw-r--r--build/tasks/lib/verifyNodeVersion.js12
-rw-r--r--build/tasks/minify.js114
-rw-r--r--build/tasks/node_smoke_tests.js97
-rw-r--r--build/tasks/npmcopy.js42
-rw-r--r--build/tasks/promises_aplus_tests.js45
-rw-r--r--build/tasks/qunit-fixture.js17
-rw-r--r--build/tasks/qunit_fixture.js22
16 files changed, 777 insertions, 544 deletions
diff --git a/build/command.js b/build/command.js
new file mode 100755
index 000000000..ee1a153bc
--- /dev/null
+++ b/build/command.js
@@ -0,0 +1,75 @@
+"use strict";
+
+const { build } = require( "./tasks/build" );
+const yargs = require( "yargs/yargs" );
+const slimExclude = require( "./tasks/lib/slim-exclude" );
+
+const argv = yargs( process.argv.slice( 2 ) )
+ .version( false )
+ .command( {
+ command: "[options]",
+ describe: "Build a jQuery bundle"
+ } )
+ .option( "filename", {
+ alias: "f",
+ type: "string",
+ description:
+ "Set the filename of the built file. Defaults to jquery.js."
+ } )
+ .option( "dir", {
+ alias: "d",
+ type: "string",
+ description:
+ "Set the dir to which to output the built file. Defaults to /dist."
+ } )
+ .option( "version", {
+ alias: "v",
+ type: "string",
+ description:
+ "Set the version to include in the built file. " +
+ "Defaults to the version in package.json plus the " +
+ "short commit SHA and any excluded modules."
+ } )
+ .option( "watch", {
+ alias: "w",
+ type: "boolean",
+ description:
+ "Watch the source files and rebuild when they change."
+ } )
+ .option( "exclude", {
+ alias: "e",
+ type: "array",
+ description:
+ "Modules to exclude from the build. " +
+ "Specifying this option will cause the " +
+ "specified modules to be excluded from the build."
+ } )
+ .option( "include", {
+ alias: "i",
+ type: "array",
+ description:
+ "Modules to include in the build. " +
+ "Specifying this option will override the " +
+ "default included modules and only include these modules."
+ } )
+ .option( "esm", {
+ type: "boolean",
+ description:
+ "Build an ES module (ESM) bundle. " +
+ "By default, a UMD bundle is built."
+ } )
+ .option( "slim", {
+ alias: "s",
+ type: "boolean",
+ description: "Build a slim bundle, which excludes " +
+ slimExclude.join( ", " )
+ } )
+ .option( "amd", {
+ type: "string",
+ description:
+ "Set the name of the AMD module. Leave blank to make an anonymous module."
+ } )
+ .help()
+ .argv;
+
+build( argv );
diff --git a/build/tasks/testswarm.js b/build/grunt-tasks/testswarm.js
index d2653e0e0..d2653e0e0 100644
--- a/build/tasks/testswarm.js
+++ b/build/grunt-tasks/testswarm.js
diff --git a/build/tasks/build.js b/build/tasks/build.js
index 3fc37da8a..1a0d7d75a 100644
--- a/build/tasks/build.js
+++ b/build/tasks/build.js
@@ -6,350 +6,356 @@
"use strict";
-module.exports = function( grunt ) {
- const fs = require( "fs" );
- const path = require( "path" );
- const rollup = require( "rollup" );
- const slimBuildFlags = require( "./lib/slim-build-flags" );
- const rollupFileOverrides = require( "./lib/rollup-plugin-file-overrides" );
- const srcFolder = path.resolve( `${ __dirname }/../../src` );
- const read = function( fileName ) {
- return grunt.file.read( `${ srcFolder }/${ fileName }` );
- };
+const fs = require( "fs" );
+const path = require( "path" );
+const util = require( "util" );
+const exec = util.promisify( require( "child_process" ).exec );
+const rollup = require( "rollup" );
+const excludedFromSlim = require( "./lib/slim-exclude" );
+const rollupFileOverrides = require( "./lib/rollup-plugin-file-overrides" );
+const pkg = require( "../../package.json" );
+const isCleanWorkingDir = require( "./lib/isCleanWorkingDir" );
+const minify = require( "./minify" );
+const getTimestamp = require( "./lib/getTimestamp" );
+const verifyNodeVersion = require( "./lib/verifyNodeVersion" );
+const srcFolder = path.resolve( __dirname, "../../src" );
+
+const minimum = [ "core" ];
+
+// Exclude specified modules if the module matching the key is removed
+const removeWith = {
+ ajax: [ "manipulation/_evalUrl", "deprecated/ajax-event-alias" ],
+ callbacks: [ "deferred" ],
+ css: [ "effects", "dimensions", "offset" ],
+ "css/showHide": [ "effects" ],
+ deferred: {
+ remove: [ "ajax", "effects", "queue", "core/ready" ],
+ include: [ "core/ready-no-deferred" ]
+ },
+ event: [ "deprecated/ajax-event-alias", "deprecated/event" ],
+ selector: [ "css/hiddenVisibleSelectors", "effects/animatedSelector" ]
+};
- const inputFileName = "jquery.js";
- const inputRollupOptions = {
- input: `${ srcFolder }/${ inputFileName }`
- };
+async function read( filename ) {
+ return fs.promises.readFile( path.join( srcFolder, filename ), "utf8" );
+}
+
+// Remove the src folder and file extension
+// and ensure unix-style path separators
+function moduleName( filename ) {
+ return filename
+ .replace( `${srcFolder}${path.sep}`, "" )
+ .replace( /\.js$/, "" )
+ .split( path.sep )
+ .join( path.posix.sep );
+}
+
+async function readdirRecursive( dir, all = [] ) {
+ let files;
+ try {
+ files = await fs.promises.readdir( path.join( srcFolder, dir ), {
+ withFileTypes: true
+ } );
+ } catch ( e ) {
+ return all;
+ }
+ for ( const file of files ) {
+ const filepath = path.join( dir, file.name );
- function getOutputRollupOptions( {
- esm = false
- } = {} ) {
- const wrapperFileName = `wrapper${ esm ? "-esm" : "" }.js`;
+ if ( file.isDirectory() ) {
+ all.push( ...( await readdirRecursive( filepath ) ) );
+ } else {
+ all.push( moduleName( filepath ) );
+ }
+ }
+ return all;
+}
- // Catch `// @CODE` and subsequent comment lines event if they don't start
- // in the first column.
- const wrapper = read( wrapperFileName )
- .split( /[\x20\t]*\/\/ @CODE\n(?:[\x20\t]*\/\/[^\n]+\n)*/ );
+async function getOutputRollupOptions( { esm = false } = {} ) {
+ const wrapperFileName = `wrapper${esm ? "-esm" : ""}.js`;
+ const wrapperSource = await read( wrapperFileName );
- return {
+ // Catch `// @CODE` and subsequent comment lines event if they don't start
+ // in the first column.
+ const wrapper = wrapperSource.split(
+ /[\x20\t]*\/\/ @CODE\n(?:[\x20\t]*\/\/[^\n]+\n)*/
+ );
- // The ESM format is not actually used as we strip it during the
- // build, inserting our own wrappers; it's just that it doesn't
- // generate any extra wrappers so there's nothing for us to remove.
- format: "esm",
+ return {
- intro: `${ wrapper[ 0 ].replace( /\n*$/, "" ) }`,
- outro: wrapper[ 1 ].replace( /^\n*/, "" )
- };
- }
+ // The ESM format is not actually used as we strip it during the
+ // build, inserting our own wrappers; it's just that it doesn't
+ // generate any extra wrappers so there's nothing for us to remove.
+ format: "esm",
- const fileOverrides = new Map();
+ intro: wrapper[ 0 ].replace( /\n*$/, "" ),
+ outro: wrapper[ 1 ].replace( /^\n*/, "" )
+ };
+}
- function getOverride( filePath ) {
- return fileOverrides.get( path.resolve( filePath ) );
- }
+const fileOverrides = new Map();
- function setOverride( filePath, source ) {
+function setOverride( filePath, source ) {
- // We want normalized paths in overrides as they will be matched
- // against normalized paths in the file overrides Rollup plugin.
- fileOverrides.set( path.resolve( filePath ), source );
- }
+ // We want normalized paths in overrides as they will be matched
+ // against normalized paths in the file overrides Rollup plugin.
+ fileOverrides.set( path.resolve( filePath ), source );
+}
- grunt.registerMultiTask(
- "build",
- "Build jQuery ECMAScript modules, " +
- "(include/exclude modules with +/- flags), embed date/version",
- async function() {
- const done = this.async();
-
- try {
- const flags = this.flags;
- const optIn = flags[ "*" ];
- let name = grunt.option( "filename" );
- const esm = !!grunt.option( "esm" );
- const distFolder = grunt.option( "dist-folder" );
- const minimum = this.data.minimum;
- const removeWith = this.data.removeWith;
- const excluded = [];
- const included = [];
- let version = grunt.config( "pkg.version" );
-
- // We'll skip printing the whole big exclusions for a bare `build:*:*:slim` which
- // usually comes from `custom:slim`.
- const isPureSlim = !!( flags.slim && flags[ "*" ] &&
- Object.keys( flags ).length === 2 );
-
- delete flags[ "*" ];
-
- if ( flags.slim ) {
- delete flags.slim;
- for ( const flag of slimBuildFlags ) {
- flags[ flag ] = true;
- }
- }
+function unique( array ) {
+ return [ ...new Set( array ) ];
+}
+async function checkExclude( exclude, include ) {
+ const included = [ ...include ];
+ const excluded = [ ...exclude ];
- /**
- * Recursively calls the excluder to remove on all modules in the list
- * @param {Array} list
- * @param {String} [prepend] Prepend this to the module name.
- * Indicates we're walking a directory
- */
- const excludeList = ( list, prepend ) => {
- if ( list ) {
- prepend = prepend ? `${ prepend }/` : "";
- list.forEach( function( module ) {
-
- // Exclude var modules as well
- if ( module === "var" ) {
- excludeList(
- fs.readdirSync( `${ srcFolder }/${ prepend }${ module }` ),
- prepend + module
- );
- return;
- }
- if ( prepend ) {
-
- // Skip if this is not a js file and we're walking files in a dir
- if ( !( module = /([\w-\/]+)\.js$/.exec( module ) ) ) {
- return;
- }
-
- // Prepend folder name if passed
- // Remove .js extension
- module = prepend + module[ 1 ];
- }
-
- // Avoid infinite recursion
- if ( excluded.indexOf( module ) === -1 ) {
- excluder( "-" + module );
- }
- } );
- }
- };
-
- /**
- * Adds the specified module to the excluded or included list, depending on the flag
- * @param {String} flag A module path relative to
- * the src directory starting with + or - to indicate
- * whether it should be included or excluded
- */
- const excluder = flag => {
- let additional;
- const m = /^(\+|-|)([\w\/-]+)$/.exec( flag );
- const exclude = m[ 1 ] === "-";
- const module = m[ 2 ];
-
- if ( exclude ) {
-
- // Can't exclude certain modules
- if ( minimum.indexOf( module ) === -1 ) {
-
- // Add to excluded
- if ( excluded.indexOf( module ) === -1 ) {
- grunt.log.writeln( flag );
- excluded.push( module );
-
- // Exclude all files in the folder of the same name
- // These are the removable dependencies
- // It's fine if the directory is not there
- try {
-
- // `selector` is a special case as we don't just remove
- // the module, but we replace it with `selector-native`
- // which re-uses parts of the `src/selector` folder.
- if ( module !== "selector" ) {
- excludeList(
- fs.readdirSync( `${ srcFolder }/${ module }` ),
- module
- );
- }
- } catch ( e ) {
- grunt.verbose.writeln( e );
- }
- }
-
- additional = removeWith[ module ];
-
- // Check removeWith list
- if ( additional ) {
- excludeList( additional.remove || additional );
- if ( additional.include ) {
- included.push( ...additional.include );
- grunt.log.writeln( "+" + additional.include );
- }
- }
- } else {
- grunt.log.error( "Module \"" + module + "\" is a minimum requirement." );
- }
- } else {
- grunt.log.writeln( flag );
- included.push( module );
- }
- };
-
- // Filename can be passed to the command line using
- // command line options
- // e.g. grunt build --filename=jquery-custom.js
- name = name ? `${ distFolder }/${ name }` : this.data.dest;
-
- // append commit id to version
- if ( process.env.COMMIT ) {
- version += " " + process.env.COMMIT;
- }
+ for ( const module of exclude ) {
+ if ( minimum.indexOf( module ) !== -1 ) {
+ throw new Error( `Module \"${module}\" is a minimum requirement.` );
+ }
- // figure out which files to exclude based on these rules in this order:
- // dependency explicit exclude
- // > explicit exclude
- // > explicit include
- // > dependency implicit exclude
- // > implicit exclude
- // examples:
- // * none (implicit exclude)
- // *:* all (implicit include)
- // *:*:-css all except css and dependents (explicit > implicit)
- // *:*:-css:+effects same (excludes effects because explicit include is
- // trumped by explicit exclude of dependency)
- // *:+effects none except effects and its dependencies
- // (explicit include trumps implicit exclude of dependency)
- for ( const flag in flags ) {
- excluder( flag );
- }
+ // Exclude all files in the dir of the same name
+ // These are the removable dependencies
+ // It's fine if the directory is not there
+ // `selector` is a special case as we don't just remove
+ // the module, but we replace it with `selector-native`
+ // which re-uses parts of the `src/selector` dir.
+ if ( module !== "selector" ) {
+ const files = await readdirRecursive( module );
+ excluded.push( ...files );
+ }
- // Remove the jQuery export from the entry file, we'll use our own
- // custom wrapper.
- setOverride( inputRollupOptions.input,
- read( inputFileName ).replace( /\n*export \{ jQuery, jQuery as \$ };\n*/, "\n" ) );
-
- // Replace exports/global with a noop noConflict
- if ( excluded.includes( "exports/global" ) ) {
- const index = excluded.indexOf( "exports/global" );
- setOverride( `${ srcFolder }/exports/global.js`,
- "import jQuery from \"../core.js\";\n\n" +
- "jQuery.noConflict = function() {};" );
- excluded.splice( index, 1 );
- }
+ // Check removeWith list
+ const additional = removeWith[ module ];
+ if ( additional ) {
+ const [ additionalExcluded, additionalIncluded ] = await checkExclude(
+ additional.remove || additional,
+ additional.include || []
+ );
+ excluded.push( ...additionalExcluded );
+ included.push( ...additionalIncluded );
+ }
+ }
+
+ return [ unique( excluded ), unique( included ) ];
+}
+
+async function writeCompiled( { code, dir, filename, version } ) {
+ const compiledContents = code
+
+ // Embed Version
+ .replace( /@VERSION/g, version )
+
+ // Embed Date
+ // yyyy-mm-ddThh:mmZ
+ .replace( /@DATE/g, new Date().toISOString().replace( /:\d+\.\d+Z$/, "Z" ) );
+
+ await fs.promises.writeFile( path.join( dir, filename ), compiledContents );
+ console.log( `[${getTimestamp()}] ${filename} v${version} created.` );
+}
+
+// Build jQuery ECMAScript modules
+async function build( {
+ amd,
+ dir = "dist",
+ exclude = [],
+ filename = "jquery.js",
+ include = [],
+ esm = false,
+ slim = false,
+ version,
+ watch = false
+} = {} ) {
+ const pureSlim = slim && !exclude.length && !include.length;
+
+ // Add the short commit hash to the version string
+ // when the version is not for a release.
+ if ( !version ) {
+ const { stdout } = await exec( "git rev-parse --short HEAD" );
+ const isClean = await isCleanWorkingDir();
+
+ // "+[slim.]SHA" is semantically correct
+ // Add ".dirty" as well if the working dir is not clean
+ version = `${pkg.version}+${slim ? "slim." : ""}${stdout.trim()}${isClean ? "" : ".dirty"}`;
+ } else if ( slim ) {
+ version += "+slim";
+ }
+
+ await fs.promises.mkdir( dir, { recursive: true } );
+
+ // Exclude slim modules when slim is true
+ const [ excluded, included ] = await checkExclude(
+ slim ? exclude.concat( excludedFromSlim ) : exclude,
+ include
+ );
+
+ // Replace exports/global with a noop noConflict
+ if ( excluded.includes( "exports/global" ) ) {
+ const index = excluded.indexOf( "exports/global" );
+ setOverride(
+ `${srcFolder}/exports/global.js`,
+ "import { jQuery } from \"../core.js\";\n\n" +
+ "jQuery.noConflict = function() {};"
+ );
+ excluded.splice( index, 1 );
+ }
- // Set a desired AMD name.
- let amdName = grunt.option( "amd" );
- if ( amdName != null ) {
- if ( amdName ) {
- grunt.log.writeln( "Naming jQuery with AMD name: " + amdName );
- } else {
- grunt.log.writeln( "AMD name now anonymous" );
- }
+ // Set a desired AMD name.
+ if ( amd != null ) {
+ if ( amd ) {
+ console.log( "Naming jQuery with AMD name: " + amd );
+ } else {
+ console.log( "AMD name now anonymous" );
+ }
+
+ // Replace the AMD name in the AMD export
+ // No name means an anonymous define
+ const amdExportContents = await read( "exports/amd.js" );
+ setOverride(
+ `${srcFolder}/exports/amd.js`,
+ amdExportContents.replace(
// Remove the comma for anonymous defines
- setOverride( `${ srcFolder }/exports/amd.js`,
- read( "exports/amd.js" )
- .replace( /(\s*)"jquery"(,\s*)/,
- amdName ? "$1\"" + amdName + "\"$2" : "" ) );
- }
+ /(\s*)"jquery"(,\s*)/,
+ amd ? `$1\"${amd}\"$2` : " "
+ )
+ );
+ }
- grunt.verbose.writeflags( excluded, "Excluded" );
- grunt.verbose.writeflags( included, "Included" );
+ // Append excluded modules to version.
+ // Skip adding exclusions for slim builds.
+ // Don't worry about semver syntax for these.
+ if ( !pureSlim && excluded.length ) {
+ version += " -" + excluded.join( ",-" );
+ }
- // Indicate a Slim build without listing all the exclusions
- // to save space.
- if ( isPureSlim ) {
- version += " slim";
+ // Append extra included modules to version.
+ if ( !pureSlim && included.length ) {
+ version += " +" + included.join( ",+" );
+ }
- // Append excluded modules to version.
- } else if ( excluded.length ) {
- version += " -" + excluded.join( ",-" );
- }
+ const inputOptions = {
+ input: `${srcFolder}/jquery.js`
+ };
- if ( excluded.length ) {
-
- // Set pkg.version to version with excludes or with the "slim" marker,
- // so minified file picks it up but skip the commit hash the same way
- // it's done for the full build.
- const commitlessVersion = version.replace( " " + process.env.COMMIT, "" );
- grunt.config.set( "pkg.version", commitlessVersion );
- grunt.verbose.writeln( "Version changed to " + commitlessVersion );
-
- // Replace excluded modules with empty sources.
- for ( const module of excluded ) {
- setOverride(
- `${ srcFolder }/${ module }.js`,
-
- // The `selector` module is not removed, but replaced
- // with `selector-native`.
- module === "selector" ? read( "selector-native.js" ) : ""
- );
- }
- }
+ const includedImports = included
+ .map( ( module ) => `import "./${module}.js";` )
+ .join( "\n" );
- // Turn off opt-in if necessary
- if ( !optIn ) {
+ const jQueryFileContents = await read( "jquery.js" );
+ if ( include.length ) {
- // Remove the default inclusions, they will be overwritten with the explicitly
- // included ones.
- setOverride( inputRollupOptions.input, "" );
+ // If include is specified, only add those modules.
+ setOverride( inputOptions.input, includedImports );
+ } else {
- }
+ // Remove the jQuery export from the entry file, we'll use our own
+ // custom wrapper.
+ setOverride(
+ inputOptions.input,
+ jQueryFileContents.replace( /\n*export \{ jQuery, jQuery as \$ };\n*/, "\n" ) +
+ includedImports
+ );
+ }
- // Import the explicitly included modules.
- if ( included.length ) {
- setOverride( inputRollupOptions.input,
- getOverride( inputRollupOptions.input ) + included
- .map( module => `import "./${module}.js";` )
- .join( "\n" ) );
- }
+ // Replace excluded modules with empty sources.
+ for ( const module of excluded ) {
+ setOverride(
+ `${srcFolder}/${module}.js`,
- const bundle = await rollup.rollup( {
- ...inputRollupOptions,
- plugins: [ rollupFileOverrides( fileOverrides ) ]
- } );
+ // The `selector` module is not removed, but replaced
+ // with `selector-native`.
+ module === "selector" ? await read( "selector-native.js" ) : ""
+ );
+ }
- const outputRollupOptions =
- getOutputRollupOptions( { esm } );
+ const bundle = await rollup.rollup( {
+ ...inputOptions,
+ plugins: [ rollupFileOverrides( fileOverrides ) ]
+ } );
- const { output: [ { code } ] } = await bundle.generate( outputRollupOptions );
+ const outputOptions = await getOutputRollupOptions( { esm } );
- const compiledContents = code
+ if ( watch ) {
+ const watcher = rollup.watch( {
+ ...inputOptions,
+ output: [ outputOptions ],
+ plugins: [ rollupFileOverrides( fileOverrides ) ],
+ watch: {
+ include: `${srcFolder}/**`,
+ skipWrite: true
+ }
+ } );
+
+ watcher.on( "event", async( event ) => {
+ switch ( event.code ) {
+ case "ERROR":
+ console.error( event.error );
+ break;
+ case "BUNDLE_END":
+ const {
+ output: [ { code } ]
+ } = await event.result.generate( outputOptions );
+
+ await writeCompiled( {
+ code,
+ dir,
+ filename,
+ version
+ } );
- // Embed Version
- .replace( /@VERSION/g, version )
+ await minify( { dir, filename, esm } );
+ break;
+ }
+ } );
- // Embed Date
- // yyyy-mm-ddThh:mmZ
- .replace(
- /@DATE/g,
- ( new Date() ).toISOString()
- .replace( /:\d+\.\d+Z$/, "Z" )
- );
+ return watcher;
+ } else {
+ const {
+ output: [ { code } ]
+ } = await bundle.generate( outputOptions );
- grunt.file.write( name, compiledContents );
- grunt.log.ok( `File '${ name }' created.` );
- done();
- } catch ( err ) {
- done( err );
- }
- } );
+ await writeCompiled( { code, dir, filename, version } );
+ await minify( { dir, filename, esm } );
+ }
+}
+
+async function buildDefaultFiles( { version, watch } = {} ) {
+ await Promise.all( [
+ build( { version, watch } ),
+ build( { filename: "jquery.slim.js", slim: true, version, watch } ),
+ build( {
+ dir: "dist-module",
+ filename: "jquery.module.js",
+ esm: true,
+ version,
+ watch
+ } ),
+ build( {
+ dir: "dist-module",
+ filename: "jquery.slim.module.js",
+ esm: true,
+ slim: true,
+ version,
+ watch
+ } )
+ ] );
+
+ // Earlier Node.js versions do not support the ESM format.
+ if ( !verifyNodeVersion() ) {
+ return;
+ }
- // Special "alias" task to make custom build creation less grawlix-y
- // Translation example
- //
- // grunt custom:+ajax,-dimensions,-effects,-offset
- //
- // Becomes:
- //
- // grunt build:*:*:+ajax:-dimensions:-effects:-offset
- //
- // There's also a special "slim" alias that resolves to the jQuery Slim build
- // configuration:
- //
- // grunt custom:slim
- grunt.registerTask( "custom", function() {
- const args = this.args;
- const modules = args.length ?
- args[ 0 ].split( "," ).join( ":" ) :
- "";
-
- grunt.log.writeln( "Creating custom build...\n" );
- grunt.task.run( [ "build:*:*" + ( modules ? ":" + modules : "" ), "minify", "dist" ] );
+ const { compareSize } = await import( "./compare_size.mjs" );
+ return compareSize( {
+ files: [
+ "dist/jquery.min.js",
+ "dist/jquery.slim.min.js",
+ "dist-module/jquery.module.min.js",
+ "dist-module/jquery.slim.module.min.js"
+ ]
} );
-};
+}
+
+module.exports = { build, buildDefaultFiles };
diff --git a/build/tasks/compare_size.mjs b/build/tasks/compare_size.mjs
new file mode 100644
index 000000000..fd6d8e72f
--- /dev/null
+++ b/build/tasks/compare_size.mjs
@@ -0,0 +1,128 @@
+import chalk from "chalk";
+import fs from "node:fs";
+import { promisify } from "node:util";
+import zlib from "node:zlib";
+import { exec as nodeExec } from "node:child_process";
+import isCleanWorkingDir from "./lib/isCleanWorkingDir.js";
+
+const gzip = promisify( zlib.gzip );
+const exec = promisify( nodeExec );
+
+async function getBranchName() {
+ const { stdout } = await exec( "git rev-parse --abbrev-ref HEAD" );
+ return stdout.trim();
+}
+
+async function getCache( loc ) {
+ try {
+ const contents = await fs.promises.readFile( loc, "utf8" );
+ return JSON.parse( contents );
+ } catch ( err ) {
+ return {};
+ }
+}
+
+function saveCache( loc, cache ) {
+ return fs.promises.writeFile( loc, JSON.stringify( cache ) );
+}
+
+function compareSizes( existing, current, padLength ) {
+ if ( typeof current !== "number" ) {
+ return chalk.grey( `${existing}`.padStart( padLength ) );
+ }
+ const delta = current - existing;
+ if ( delta > 0 ) {
+ return chalk.red( `+${delta}`.padStart( padLength ) );
+ }
+ return chalk.green( `${delta}`.padStart( padLength ) );
+}
+
+export async function compareSize( { cache = ".sizecache.json", files } = {} ) {
+ if ( !files || !files.length ) {
+ throw new Error( "No files specified" );
+ }
+
+ const branch = await getBranchName();
+ const sizeCache = await getCache( cache );
+
+ let rawPadLength = 0;
+ let gzPadLength = 0;
+ const results = await Promise.all(
+ files.map( async function( filename ) {
+
+ let contents = await fs.promises.readFile( filename, "utf8" );
+
+ // Remove the banner for size comparisons.
+ // The version string can vary widely by short SHA.
+ contents = contents.replace( /\/\*\! jQuery[^\n]+/, "" );
+
+ const size = Buffer.byteLength( contents, "utf8" );
+ const gzippedSize = ( await gzip( contents ) ).length;
+
+ // Add one to give space for the `+` or `-` in the comparison
+ rawPadLength = Math.max( rawPadLength, size.toString().length + 1 );
+ gzPadLength = Math.max( gzPadLength, gzippedSize.toString().length + 1 );
+
+ return { filename, raw: size, gz: gzippedSize };
+ } )
+ );
+
+ const header = "raw".padStart( rawPadLength ) +
+ "gz".padStart( gzPadLength + 1 ) +
+ " Filename";
+
+ const sizes = results.map( function( result ) {
+ const rawSize = result.raw.toString().padStart( rawPadLength );
+ const gzSize = result.gz.toString().padStart( gzPadLength );
+ return `${rawSize} ${gzSize} ${result.filename}`;
+ } );
+
+ const comparisons = Object.keys( sizeCache ).map( function( branch ) {
+ const branchSizes = Object.keys( sizeCache[ branch ] ).map( function( filename ) {
+ const branchResult = sizeCache[ branch ][ filename ];
+ const compareResult = results.find( function( result ) {
+ return result.filename === filename;
+ } ) || {};
+
+ const compareRaw = compareSizes( branchResult.raw, compareResult.raw, rawPadLength );
+ const compareGz = compareSizes( branchResult.gz, compareResult.gz, gzPadLength );
+ return `${compareRaw} ${compareGz} ${filename}`;
+ } );
+
+ return [
+ "", // New line before each branch
+ chalk.bold( branch ),
+ header,
+ ...branchSizes
+ ].join( "\n" );
+ } );
+
+ const output = [
+ "", // Opening new line
+ chalk.bold( "Sizes" ),
+ header,
+ ...sizes,
+ ...comparisons,
+ "" // Closing new line
+ ].join( "\n" );
+
+ console.log( output );
+
+ // Only save cache for the current branch
+ // if the working directory is clean.
+ if ( await isCleanWorkingDir() ) {
+ sizeCache[ branch ] = {};
+ results.forEach( function( result ) {
+ sizeCache[ branch ][ result.filename ] = {
+ raw: result.raw,
+ gz: result.gz
+ };
+ } );
+
+ await saveCache( cache, sizeCache );
+
+ console.log( `Saved cache for ${branch}.` );
+ }
+
+ return results;
+}
diff --git a/build/tasks/dist.js b/build/tasks/dist.js
index 36ce38b00..d6488aa1b 100644
--- a/build/tasks/dist.js
+++ b/build/tasks/dist.js
@@ -1,72 +1,31 @@
"use strict";
-module.exports = function( grunt ) {
- const fs = require( "fs" );
- const filename = grunt.option( "filename" );
- const distFolder = grunt.option( "dist-folder" );
- const distPaths = [
- `${ distFolder }/${ filename }`,
- `${ distFolder }/${ filename.replace( ".js", ".min.js" ) }`,
- `${ distFolder }/${ filename.replace( ".js", ".min.map" ) }`
- ];
-
- // Process files for distribution
- grunt.registerTask( "dist", function() {
- let stored, flags, paths, nonascii;
-
- // Check for stored destination paths
- // ( set in dist/.destination.json )
- stored = Object.keys( grunt.config( "dst" ) );
-
- // Allow command line input as well
- flags = Object.keys( this.flags );
-
- // Combine all output target paths
- paths = [].concat( stored, flags ).filter( function( path ) {
- return path !== "*";
- } );
-
- // Ensure the dist files are pure ASCII
- nonascii = false;
-
- distPaths.forEach( function( filename ) {
- let i, c;
- const text = fs.readFileSync( filename, "utf8" );
-
- // Ensure files use only \n for line endings, not \r\n
- if ( /\x0d\x0a/.test( text ) ) {
- grunt.log.writeln( filename + ": Incorrect line endings (\\r\\n)" );
- nonascii = true;
+// Process files for distribution.
+module.exports = async function processForDist( text, filename ) {
+ if ( !text ) {
+ throw new Error( "text required for processForDist" );
+ }
+
+ if ( !filename ) {
+ throw new Error( "filename required for processForDist" );
+ }
+
+ // Ensure files use only \n for line endings, not \r\n
+ if ( /\x0d\x0a/.test( text ) ) {
+ throw new Error( filename + ": Incorrect line endings (\\r\\n)" );
+ }
+
+ // Ensure only ASCII chars so script tags don't need a charset attribute
+ if ( text.length !== Buffer.byteLength( text, "utf8" ) ) {
+ let message = filename + ": Non-ASCII characters detected:\n";
+ for ( let i = 0; i < text.length; i++ ) {
+ const c = text.charCodeAt( i );
+ if ( c > 127 ) {
+ message += "- position " + i + ": " + c + "\n";
+ message += "==> " + text.substring( i - 20, i + 20 );
+ break;
}
-
- // Ensure only ASCII chars so script tags don't need a charset attribute
- if ( text.length !== Buffer.byteLength( text, "utf8" ) ) {
- grunt.log.writeln( filename + ": Non-ASCII characters detected:" );
- for ( i = 0; i < text.length; i++ ) {
- c = text.charCodeAt( i );
- if ( c > 127 ) {
- grunt.log.writeln( "- position " + i + ": " + c );
- grunt.log.writeln( "-- " + text.substring( i - 20, i + 20 ) );
- break;
- }
- }
- nonascii = true;
- }
-
- // Optionally copy dist files to other locations
- paths.forEach( function( path ) {
- let created;
-
- if ( !/\/$/.test( path ) ) {
- path += "/";
- }
-
- created = path + filename.replace( "dist/", "" );
- grunt.file.write( created, text );
- grunt.log.writeln( "File '" + created + "' created." );
- } );
- } );
-
- return !nonascii;
- } );
+ }
+ throw new Error( message );
+ }
};
diff --git a/build/tasks/lib/getTimestamp.js b/build/tasks/lib/getTimestamp.js
new file mode 100644
index 000000000..c3c8aed11
--- /dev/null
+++ b/build/tasks/lib/getTimestamp.js
@@ -0,0 +1,9 @@
+"use strict";
+
+module.exports = function getTimestamp() {
+ const now = new Date();
+ const hours = now.getHours().toString().padStart( 2, "0" );
+ const minutes = now.getMinutes().toString().padStart( 2, "0" );
+ const seconds = now.getSeconds().toString().padStart( 2, "0" );
+ return `${hours}:${minutes}:${seconds}`;
+};
diff --git a/build/tasks/lib/isCleanWorkingDir.js b/build/tasks/lib/isCleanWorkingDir.js
new file mode 100644
index 000000000..16c87fd9d
--- /dev/null
+++ b/build/tasks/lib/isCleanWorkingDir.js
@@ -0,0 +1,9 @@
+"use strict";
+
+const util = require( "util" );
+const exec = util.promisify( require( "child_process" ).exec );
+
+module.exports = async function isCleanWorkingDir() {
+ const { stdout } = await exec( "git status --untracked-files=no --porcelain" );
+ return !stdout.trim();
+};
diff --git a/build/tasks/lib/slim-build-flags.js b/build/tasks/lib/slim-exclude.js
index a3574df21..cc74cbac5 100644
--- a/build/tasks/lib/slim-build-flags.js
+++ b/build/tasks/lib/slim-exclude.js
@@ -2,9 +2,9 @@
// NOTE: keep it in sync with test/data/testinit.js
module.exports = [
- "-ajax",
- "-callbacks",
- "-deferred",
- "-effects",
- "-queue"
+ "ajax",
+ "callbacks",
+ "deferred",
+ "effects",
+ "queue"
];
diff --git a/build/tasks/lib/spawn_test.js b/build/tasks/lib/spawn_test.js
deleted file mode 100644
index 146155411..000000000
--- a/build/tasks/lib/spawn_test.js
+++ /dev/null
@@ -1,16 +0,0 @@
-"use strict";
-
-// Run Node with provided parameters: the first one being the Grunt
-// done function and latter ones being files to be tested.
-// See the comment in ../node_smoke_tests.js for more information.
-module.exports = function spawnTest( done, command ) {
- var spawn = require( "child_process" ).spawn;
-
- spawn( command, {
- stdio: "inherit",
- shell: true
- } )
- .on( "close", function( code ) {
- done( code === 0 );
- } );
-};
diff --git a/build/tasks/lib/verifyNodeVersion.js b/build/tasks/lib/verifyNodeVersion.js
new file mode 100644
index 000000000..80d57ed6e
--- /dev/null
+++ b/build/tasks/lib/verifyNodeVersion.js
@@ -0,0 +1,12 @@
+"use strict";
+
+const { version } = require( "process" );
+const nodeV16OrNewer = !/^v1[0-5]\./.test( version );
+
+module.exports = function verifyNodeVersion() {
+ if ( !nodeV16OrNewer ) {
+ console.log( "Old Node.js detected, task skipped..." );
+ return false;
+ }
+ return true;
+};
diff --git a/build/tasks/minify.js b/build/tasks/minify.js
index 6d3c6c568..c5e0ff25f 100644
--- a/build/tasks/minify.js
+++ b/build/tasks/minify.js
@@ -1,57 +1,67 @@
-/**
- * Minify JavaScript using SWC.
- */
-
"use strict";
-module.exports = ( grunt ) => {
- const swc = require( "@swc/core" );
-
- grunt.registerMultiTask(
- "minify",
- "Minify JavaScript using SWC",
- async function() {
- const done = this.async();
- const options = this.options();
- const sourceMapFilename = options.sourceMap && options.sourceMap.filename;
- const sourceMapOverrides = options.sourceMap && options.sourceMap.overrides || {};
-
- await Promise.all( this.files.map( async( { src, dest } ) => {
- if ( src.length !== 1 ) {
- grunt.fatal( "The minify task requires a single source per destination" );
- }
-
- const { code, map: incompleteMap } = await swc.minify(
- grunt.file.read( src[ 0 ] ),
- {
- ...options.swc,
- inlineSourcesContent: false,
- sourceMap: sourceMapFilename ?
- {
- filename: sourceMapFilename
- } :
- false
- }
- );
-
- // Can't seem to get SWC to not use CRLF on Windows, so replace them with LF.
- grunt.file.write( dest, code.replace( /\r\n/g, "\n" ) );
-
- if ( sourceMapFilename ) {
-
- // Apply map overrides if needed. See the task config description
- // for more details.
- const mapObject = {
- ...JSON.parse( incompleteMap ),
- ...sourceMapOverrides
- };
- const map = JSON.stringify( mapObject );
-
- grunt.file.write( sourceMapFilename, map );
- }
- } ) );
-
- done();
+const swc = require( "@swc/core" );
+const fs = require( "fs" );
+const path = require( "path" );
+const processForDist = require( "./dist" );
+const getTimestamp = require( "./lib/getTimestamp" );
+
+const rjs = /\.js$/;
+
+module.exports = async function minify( { filename, dir, esm } ) {
+ const contents = await fs.promises.readFile( path.join( dir, filename ), "utf8" );
+ const version = /jQuery JavaScript Library ([^\n]+)/.exec( contents )[ 1 ];
+
+ const { code, map: incompleteMap } = await swc.minify(
+ contents,
+ {
+ compress: {
+ ecma: esm ? 2015 : 5,
+ hoist_funs: false,
+ loops: false
+ },
+ format: {
+ ecma: esm ? 2015 : 5,
+ asciiOnly: true,
+ comments: false,
+ preamble: `/*! jQuery ${version}` +
+ " | (c) OpenJS Foundation and other contributors" +
+ " | jquery.org/license */\n"
+ },
+ mangle: true,
+ inlineSourcesContent: false,
+ sourceMap: true
}
);
+
+ const minFilename = filename.replace( rjs, ".min.js" );
+ const mapFilename = filename.replace( rjs, ".min.map" );
+
+ // The map's `files` & `sources` property are set incorrectly, fix
+ // them via overrides from the task config.
+ // See https://github.com/swc-project/swc/issues/7588#issuecomment-1624345254
+ const map = JSON.stringify( {
+ ...JSON.parse( incompleteMap ),
+ file: minFilename,
+ sources: [ filename ]
+ } );
+
+ await Promise.all( [
+ fs.promises.writeFile(
+ path.join( dir, minFilename ),
+ code
+ ),
+ fs.promises.writeFile(
+ path.join( dir, mapFilename ),
+ map
+ )
+ ] );
+
+ // Always process files for dist
+ // Doing it here avoids extra file reads
+ processForDist( contents, filename );
+ processForDist( code, minFilename );
+ processForDist( map, mapFilename );
+
+ console.log( `[${getTimestamp()}] ${minFilename} ${version} with ${mapFilename} created.` );
};
diff --git a/build/tasks/node_smoke_tests.js b/build/tasks/node_smoke_tests.js
index 7edbac881..5aa7660b0 100644
--- a/build/tasks/node_smoke_tests.js
+++ b/build/tasks/node_smoke_tests.js
@@ -1,51 +1,50 @@
"use strict";
-module.exports = ( grunt ) => {
- const fs = require( "fs" );
- const spawnTest = require( "./lib/spawn_test.js" );
- const nodeV16OrNewer = !/^v1[0-5]\./.test( process.version );
-
- grunt.registerTask( "node_smoke_tests", function( moduleType, jQueryModuleSpecifier ) {
- if (
- ( moduleType !== "commonjs" && moduleType !== "module" ) ||
- !jQueryModuleSpecifier
- ) {
- grunt.fatal( "Use `node_smoke_tests:commonjs:JQUERY` " +
- "or `node_smoke_tests:module:JQUERY.\n" +
- "JQUERY can be `jquery`, `jquery/slim` or a path to any of them." );
- }
-
- if ( !nodeV16OrNewer ) {
- grunt.log.writeln( "Old Node.js detected, running the task " +
- `"node_smoke_tests:${ moduleType }:${ jQueryModuleSpecifier }" skipped...` );
- return;
- }
-
- const testsDir = `./test/node_smoke_tests/${ moduleType }`;
- const nodeSmokeTests = [];
-
- // Fire up all tests defined in test/node_smoke_tests/*.js in spawned sub-processes.
- // All the files under test/node_smoke_tests/*.js are supposed to exit with 0 code
- // on success or another one on failure. Spawning in sub-processes is
- // important so that the tests & the main process don't interfere with
- // each other, e.g. so that they don't share the `require` cache.
-
- fs.readdirSync( testsDir )
- .filter( ( testFilePath ) =>
- fs.statSync( `${ testsDir }/${ testFilePath }` ).isFile() &&
- /\.[cm]?js$/.test( testFilePath )
- )
- .forEach( ( testFilePath ) => {
- const taskName = `node_${ testFilePath.replace( /\.[cm]?js$/, "" ) }:${ moduleType }:${ jQueryModuleSpecifier }`;
-
- grunt.registerTask( taskName, function() {
- spawnTest( this.async(), `node "${ testsDir }/${
- testFilePath }" ${ jQueryModuleSpecifier }` );
- } );
-
- nodeSmokeTests.push( taskName );
- } );
-
- grunt.task.run( nodeSmokeTests );
- } );
-};
+const fs = require( "fs" );
+const util = require( "util" );
+const exec = util.promisify( require( "child_process" ).exec );
+const verifyNodeVersion = require( "./lib/verifyNodeVersion" );
+
+const allowedModules = [ "commonjs", "module" ];
+
+if ( !verifyNodeVersion() ) {
+ return;
+}
+
+// Fire up all tests defined in test/node_smoke_tests/*.js in spawned sub-processes.
+// All the files under test/node_smoke_tests/*.js are supposed to exit with 0 code
+// on success or another one on failure. Spawning in sub-processes is
+// important so that the tests & the main process don't interfere with
+// each other, e.g. so that they don't share the `require` cache.
+
+async function runTests( sourceType, module ) {
+ if ( !allowedModules.includes( sourceType ) ) {
+ throw new Error(
+ `Usage: \`node_smoke_tests [${allowedModules.join( "|" )}]:JQUERY\``
+ );
+ }
+ const dir = `./test/node_smoke_tests/${sourceType}`;
+ const files = await fs.promises.readdir( dir, { withFileTypes: true } );
+ const testFiles = files.filter( ( testFilePath ) => testFilePath.isFile() );
+ await Promise.all(
+ testFiles.map( ( testFile ) =>
+ exec( `node "${dir}/${testFile.name}" "${module}"` )
+ )
+ );
+ console.log( `Node smoke tests passed for ${sourceType} "${module}".` );
+}
+
+async function runDefaultTests() {
+ await Promise.all( [
+ runTests( "commonjs", "jquery" ),
+ runTests( "commonjs", "jquery/slim" ),
+ runTests( "commonjs", "./dist/jquery.js" ),
+ runTests( "commonjs", "./dist/jquery.slim.js" ),
+ runTests( "module", "jquery" ),
+ runTests( "module", "jquery/slim" ),
+ runTests( "module", "./dist-module/jquery.module.js" ),
+ runTests( "module", "./dist-module/jquery.slim.module.js" )
+ ] );
+}
+
+runDefaultTests();
diff --git a/build/tasks/npmcopy.js b/build/tasks/npmcopy.js
new file mode 100644
index 000000000..c57acc2e7
--- /dev/null
+++ b/build/tasks/npmcopy.js
@@ -0,0 +1,42 @@
+"use strict";
+
+const fs = require( "fs" );
+const path = require( "path" );
+
+const projectDir = path.resolve( __dirname, "..", ".." );
+
+const files = {
+ "bootstrap/bootstrap.css": "bootstrap/dist/css/bootstrap.css",
+ "bootstrap/bootstrap.min.css": "bootstrap/dist/css/bootstrap.min.css",
+ "bootstrap/bootstrap.min.css.map": "bootstrap/dist/css/bootstrap.min.css.map",
+
+ "core-js-bundle/core-js-bundle.js": "core-js-bundle/minified.js",
+ "core-js-bundle/LICENSE": "core-js-bundle/LICENSE",
+
+ "npo/npo.js": "native-promise-only/lib/npo.src.js",
+
+ "qunit/qunit.js": "qunit/qunit/qunit.js",
+ "qunit/qunit.css": "qunit/qunit/qunit.css",
+ "qunit/LICENSE.txt": "qunit/LICENSE.txt",
+
+ "requirejs/require.js": "requirejs/require.js",
+
+ "sinon/sinon.js": "sinon/pkg/sinon.js",
+ "sinon/LICENSE.txt": "sinon/LICENSE"
+};
+
+async function npmcopy() {
+ await fs.promises.mkdir( path.resolve( projectDir, "external" ), {
+ recursive: true
+ } );
+ for ( const [ dest, source ] of Object.entries( files ) ) {
+ const from = path.resolve( projectDir, "node_modules", source );
+ const to = path.resolve( projectDir, "external", dest );
+ const toDir = path.dirname( to );
+ await fs.promises.mkdir( toDir, { recursive: true } );
+ await fs.promises.copyFile( from, to );
+ console.log( `${source} → ${dest}` );
+ }
+}
+
+npmcopy();
diff --git a/build/tasks/promises_aplus_tests.js b/build/tasks/promises_aplus_tests.js
index 1bbeff08e..fc94c8e02 100644
--- a/build/tasks/promises_aplus_tests.js
+++ b/build/tasks/promises_aplus_tests.js
@@ -1,27 +1,32 @@
"use strict";
-module.exports = grunt => {
- const timeout = 2000;
- const spawnTest = require( "./lib/spawn_test.js" );
+const { spawn } = require( "child_process" );
+const verifyNodeVersion = require( "./lib/verifyNodeVersion" );
+const path = require( "path" );
+const os = require( "os" );
- grunt.registerTask( "promises_aplus_tests",
- [ "promises_aplus_tests:deferred", "promises_aplus_tests:when" ] );
+if ( !verifyNodeVersion() ) {
+ return;
+}
- grunt.registerTask( "promises_aplus_tests:deferred", function() {
- spawnTest( this.async(),
- "\"" + __dirname + "/../../node_modules/.bin/promises-aplus-tests\"" +
- " test/promises_aplus_adapters/deferred.cjs" +
- " --reporter dot" +
- " --timeout " + timeout
- );
- } );
+const command = path.resolve(
+ __dirname,
+ `../../node_modules/.bin/promises-aplus-tests${os.platform() === "win32" ? ".cmd" : ""}`
+);
+const args = [ "--reporter", "dot", "--timeout", "2000" ];
+const tests = [
+ "test/promises_aplus_adapters/deferred.cjs",
+ "test/promises_aplus_adapters/when.cjs"
+];
- grunt.registerTask( "promises_aplus_tests:when", function() {
- spawnTest( this.async(),
- "\"" + __dirname + "/../../node_modules/.bin/promises-aplus-tests\"" +
- " test/promises_aplus_adapters/when.cjs" +
- " --reporter dot" +
- " --timeout " + timeout
+async function runTests() {
+ tests.forEach( ( test ) => {
+ spawn(
+ command,
+ [ test ].concat( args ),
+ { stdio: "inherit" }
);
} );
-};
+}
+
+runTests();
diff --git a/build/tasks/qunit-fixture.js b/build/tasks/qunit-fixture.js
new file mode 100644
index 000000000..3059bb929
--- /dev/null
+++ b/build/tasks/qunit-fixture.js
@@ -0,0 +1,17 @@
+"use strict";
+
+const fs = require( "fs" );
+
+async function generateFixture() {
+ const fixture = await fs.promises.readFile( "./test/data/qunit-fixture.html", "utf8" );
+ await fs.promises.writeFile(
+ "./test/data/qunit-fixture.js",
+ "// Generated by build/tasks/qunit-fixture.js\n" +
+ "QUnit.config.fixture = " +
+ JSON.stringify( fixture.replace( /\r\n/g, "\n" ) ) +
+ ";\n"
+ );
+ console.log( "Updated ./test/data/qunit-fixture.js" );
+}
+
+generateFixture();
diff --git a/build/tasks/qunit_fixture.js b/build/tasks/qunit_fixture.js
deleted file mode 100644
index 138ca662d..000000000
--- a/build/tasks/qunit_fixture.js
+++ /dev/null
@@ -1,22 +0,0 @@
-"use strict";
-
-var fs = require( "fs" );
-
-module.exports = function( grunt ) {
- grunt.registerTask( "qunit_fixture", function() {
- var dest = "./test/data/qunit-fixture.js";
- fs.writeFileSync(
- dest,
- "// Generated by build/tasks/qunit_fixture.js\n" +
- "QUnit.config.fixture = " +
- JSON.stringify(
- fs.readFileSync(
- "./test/data/qunit-fixture.html",
- "utf8"
- ).toString().replace( /\r\n/g, "\n" )
- ) +
- ";\n"
- );
- grunt.log.ok( "Updated " + dest + "." );
- } );
-};