mirror of
https://github.com/jquery/jquery.git
synced 2024-12-09 08:04:24 +00:00
2cf659189e
*Authors* - Checking and updating authors has been migrated to a custom script in the repo *Changelog* - changelogplease is no longer maintained - generate changelog in markdown for GitHub releases - generate changelog in HTML for blog posts - generate contributors list in HTML for blog posts *dist* - clone dist repo, copy files, and commit/push - commit tag with dist files on main branch; remove dist files from main branch after release *cdn* - clone cdn repo, copy files, and commit/push - create versioned and unversioned copies in cdn/ - generate md5 sums and archives for Google and MSFT *build* - implement reproducible builds and verify release builds * uses the last modified date for the latest commit * See https://reproducible-builds.org/ - the verify workflow also ensures all files were properly published to the CDN and npm *docs* - the new release workflow is documented at build/release/README.md *verify* - use the last modified date of the commit before the tag - use versioned filenames when checking map files on the CDN - skip factory and package.json files when verifying CDN *misc* - now that we don't need the jquery-release script and now that we no longer need to build on Node 10, we can use ESM in all files in the build folder - limit certain workflows to the main repo (not forks) - version has been set to the previously released version 3.7.1, as release-it expects - release-it added the `preReleaseBase` option and we now always set it to `1` in the npm script. This is a noop for stable releases. - include post-release script to be run manually after a release, with further steps that should be verified manually Ref jquery/jquery-release#114 Closes gh-5522
349 lines
9.5 KiB
JavaScript
349 lines
9.5 KiB
JavaScript
/**
|
|
* Special build task to handle various jQuery build requirements.
|
|
* Compiles JS modules into one bundle, sets the custom AMD name,
|
|
* and includes/excludes specified modules
|
|
*/
|
|
|
|
import { exec as nodeExec } from "node:child_process";
|
|
import { writeFileSync } from "node:fs";
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import util from "node:util";
|
|
import requirejs from "requirejs";
|
|
import { compareSize } from "./lib/compareSize.js";
|
|
import getTimestamp from "./lib/getTimestamp.js";
|
|
import isCleanWorkingDir from "./lib/isCleanWorkingDir.js";
|
|
import excludedFromSlim from "./lib/slim-exclude.js";
|
|
import minify from "./minify.js";
|
|
|
|
const exec = util.promisify( nodeExec );
|
|
const pkg = JSON.parse( await fs.readFile( "./package.json", "utf8" ) );
|
|
|
|
const rdefineEnd = /\}\s*?\);[^}\w]*$/;
|
|
|
|
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" ]
|
|
};
|
|
|
|
async function read( filename ) {
|
|
return fs.readFile( path.join( "./src", filename ), "utf8" );
|
|
}
|
|
|
|
// Remove the src folder and file extension
|
|
// and ensure unix-style path separators
|
|
function moduleName( filename ) {
|
|
return filename
|
|
.replace( new RegExp( `.*\\${ path.sep }src\\${ path.sep }` ), "" )
|
|
.replace( /\.js$/, "" )
|
|
.split( path.sep )
|
|
.join( path.posix.sep );
|
|
}
|
|
|
|
async function readdirRecursive( dir, all = [] ) {
|
|
let files;
|
|
try {
|
|
files = await fs.readdir( path.join( "./src", dir ), {
|
|
withFileTypes: true
|
|
} );
|
|
} catch ( _ ) {
|
|
return all;
|
|
}
|
|
for ( const file of files ) {
|
|
const filepath = path.join( dir, file.name );
|
|
|
|
if ( file.isDirectory() ) {
|
|
all.push( ...( await readdirRecursive( filepath ) ) );
|
|
} else {
|
|
all.push( moduleName( filepath ) );
|
|
}
|
|
}
|
|
return all;
|
|
}
|
|
|
|
async function getRequireConfig( { amd } = {} ) {
|
|
const wrapperSource = await read( "wrapper.js" );
|
|
|
|
// 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)*/
|
|
);
|
|
|
|
/**
|
|
* Strip all definitions generated by requirejs
|
|
* Convert "var" modules to var declarations
|
|
* "var module" means the module only contains a return
|
|
* statement that should be converted to a var declaration
|
|
* This is indicated by including the file in any "var" folder
|
|
* @param {String} name
|
|
* @param {String} path
|
|
* @param {String} contents The contents to be written (including their AMD wrappers)
|
|
*/
|
|
function convert( name, path, contents ) {
|
|
|
|
// Convert var modules
|
|
if ( /.\/var\//.test( path.replace( process.cwd(), "" ) ) ) {
|
|
contents = contents
|
|
.replace(
|
|
/define\(\s*(["'])[\w\W]*?\1[\w\W]*?return/,
|
|
"var " +
|
|
( /var\/([\w-]+)/.exec( name )[ 1 ] ) +
|
|
" ="
|
|
)
|
|
.replace( rdefineEnd, "" );
|
|
|
|
} else {
|
|
|
|
contents = contents
|
|
.replace( /\s*return\s+[^\}]+(\}\s*?\);[^\w\}]*)$/, "$1" )
|
|
|
|
// Multiple exports
|
|
.replace( /\s*exports\.\w+\s*=\s*\w+;/g, "" );
|
|
|
|
// Remove define wrappers, closure ends, and empty declarations
|
|
contents = contents
|
|
.replace( /define\([^{]*?{\s*(?:("|')use strict\1(?:;|))?/, "" )
|
|
.replace( rdefineEnd, "" );
|
|
|
|
// Remove anything wrapped with
|
|
// /* ExcludeStart */ /* ExcludeEnd */
|
|
// or a single line directly after a // BuildExclude comment
|
|
contents = contents
|
|
.replace( /\/\*\s*ExcludeStart\s*\*\/[\w\W]*?\/\*\s*ExcludeEnd\s*\*\//ig, "" )
|
|
.replace( /\/\/\s*BuildExclude\n\r?[\w\W]*?\n\r?/ig, "" );
|
|
|
|
// Remove empty definitions
|
|
contents = contents
|
|
.replace( /define\(\s*\[[^\]]*\]\s*\)[\W\n]+$/, "" );
|
|
}
|
|
|
|
// AMD Name
|
|
if ( amd != null && /^exports\/amd$/.test( name ) ) {
|
|
if ( amd ) {
|
|
console.log( "Naming jQuery with AMD name: " + amd );
|
|
} else {
|
|
console.log( "AMD name now anonymous" );
|
|
}
|
|
|
|
// Remove the comma for anonymous defines
|
|
contents = contents
|
|
.replace( /(\s*)"jquery"(\,\s*)/, amd ? "$1\"" + amd + "\"$2" : "" );
|
|
|
|
}
|
|
return contents;
|
|
}
|
|
|
|
return {
|
|
baseUrl: "src",
|
|
name: "jquery",
|
|
|
|
// Allow strict mode
|
|
useStrict: true,
|
|
|
|
// We have multiple minify steps
|
|
optimize: "none",
|
|
|
|
// Include dependencies loaded with require
|
|
findNestedDependencies: true,
|
|
|
|
// Avoid inserting define() placeholder
|
|
skipModuleInsertion: true,
|
|
|
|
// Avoid breaking semicolons inserted by r.js
|
|
skipSemiColonInsertion: true,
|
|
wrap: {
|
|
start: wrapper[ 0 ].replace( /\/\*\s*eslint(?: |-).*\s*\*\/\n/, "" ),
|
|
end: wrapper[ 1 ]
|
|
},
|
|
rawText: {},
|
|
onBuildWrite: convert
|
|
};
|
|
}
|
|
|
|
function unique( array ) {
|
|
return [ ...new Set( array ) ];
|
|
}
|
|
|
|
async function checkExclude( exclude, include ) {
|
|
const included = [ ...include ];
|
|
const excluded = [ ...exclude ];
|
|
|
|
for ( const module of exclude ) {
|
|
if ( minimum.indexOf( module ) !== -1 ) {
|
|
throw new Error( `Module \"${ module }\" is a minimum requirement.` );
|
|
}
|
|
|
|
// 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` folder.
|
|
// "sizzle" is legacy for selector
|
|
if ( module !== "selector" && module !== "sizzle" ) {
|
|
const files = await readdirRecursive( module );
|
|
excluded.push( ...files );
|
|
}
|
|
|
|
// 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 getLastModifiedDate() {
|
|
const { stdout } = await exec( "git log -1 --format=\"%at\"" );
|
|
return new Date( parseInt( stdout, 10 ) * 1000 );
|
|
}
|
|
|
|
export async function build( {
|
|
amd,
|
|
dir = "dist",
|
|
exclude = [],
|
|
filename = "jquery.js",
|
|
include = [],
|
|
slim = false,
|
|
version
|
|
} = {} ) {
|
|
|
|
// 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();
|
|
|
|
// Add "+SHA" if the version is not set.
|
|
// Add ".dirty" as well if the working dir is not clean.
|
|
version = `${ pkg.version }+${ stdout.trim() }${ isClean ? "" : ".dirty" }`;
|
|
}
|
|
|
|
await fs.mkdir( dir, { recursive: true } );
|
|
|
|
// Exclude slim modules when slim is true
|
|
const [ excluded, included ] = await checkExclude(
|
|
slim ? exclude.concat( excludedFromSlim ) : exclude,
|
|
include
|
|
);
|
|
const config = await getRequireConfig( { amd } );
|
|
|
|
// Use the last modified date so builds are reproducible
|
|
const date = process.env.RELEASE_DATE ?
|
|
new Date( process.env.RELEASE_DATE ) :
|
|
await getLastModifiedDate();
|
|
|
|
// Replace exports/global with a noop noConflict
|
|
if ( excluded.includes( "exports/global" ) ) {
|
|
const index = excluded.indexOf( "exports/global" );
|
|
config.rawText[ "exports/global" ] = "define( [\n\t\"../core\"\n], " +
|
|
"function( jQuery ) {\n\tjQuery.noConflict = function() {};\n} );";
|
|
excluded.splice( index, 1 );
|
|
}
|
|
|
|
// "sizzle" is legacy for selector
|
|
if ( excluded.indexOf( "selector" ) > -1 || excluded.indexOf( "sizzle" ) > -1 ) {
|
|
config.rawText.selector = "define( [ \"./selector-native\" ] );";
|
|
}
|
|
|
|
if ( excluded.length ) {
|
|
|
|
// Append excluded modules to version.
|
|
// Skip adding exclusions for slim builds.
|
|
// Don't worry about semver syntax for these.
|
|
if ( excluded.length ) {
|
|
version += " -" + excluded.join( ",-" );
|
|
}
|
|
|
|
// Have to use shallow or core will get excluded since it is a dependency
|
|
config.excludeShallow = excluded;
|
|
}
|
|
|
|
if ( included.length ) {
|
|
config.include = included;
|
|
|
|
// Append extra included modules to version.
|
|
if ( included.length ) {
|
|
version += " +" + included.join( ",+" );
|
|
}
|
|
|
|
// Overwrite the default inclusions with the explicit ones provided
|
|
config.rawText.jquery = "define( [\n" +
|
|
included.map( module => "\t\"./" + module + "\"" ).join( ",\n" ) +
|
|
"\n] );";
|
|
}
|
|
|
|
/**
|
|
* Handle Final output from the optimizer
|
|
* @param {String} compiled
|
|
*/
|
|
config.out = function( compiled ) {
|
|
const compiledContents = compiled
|
|
|
|
// Embed Version
|
|
.replace( /@VERSION/g, version )
|
|
|
|
// Embed Date
|
|
// yyyy-mm-ddThh:mmZ
|
|
.replace( /@DATE/g, date.toISOString().replace( /:\d+\.\d+Z$/, "Z" ) );
|
|
|
|
// Write concatenated source to file
|
|
// Cannot use async in config.out
|
|
writeFileSync(
|
|
path.join( dir, filename ),
|
|
compiledContents
|
|
);
|
|
};
|
|
|
|
await new Promise( ( resolve, reject ) => {
|
|
requirejs.optimize( config, () => {
|
|
|
|
// Wait a beat. For some reason, the write can
|
|
// take a moment after this to complete in Node 10.
|
|
setTimeout( resolve, 100 );
|
|
}, ( error ) => {
|
|
console.error( error );
|
|
reject( error );
|
|
} );
|
|
} );
|
|
|
|
console.log( `[${ getTimestamp() }] ${ filename } v${ version } created.` );
|
|
|
|
await minify( { filename, dir } );
|
|
}
|
|
|
|
export async function buildDefaultFiles( {
|
|
version = process.env.VERSION
|
|
} = {} ) {
|
|
await Promise.all( [
|
|
build( { version } ),
|
|
build( { filename: "jquery.slim.js", slim: true, version } )
|
|
] );
|
|
|
|
return compareSize( {
|
|
files: [
|
|
"dist/jquery.min.js",
|
|
"dist/jquery.slim.min.js"
|
|
]
|
|
} );
|
|
}
|