import fs from 'node:fs' import { fileURLToPath } from 'node:url' import path from 'node:path' import gulp from 'gulp' import { deleteAsync } from 'del' import log from 'fancy-log' import webpack from 'webpack' import babel from 'gulp-babel' import { mkdirp } from 'mkdirp' import { cleanup, iteratePath } from './tools/docgenerator.js' import { generateEntryFiles } from './tools/entryGenerator.js' import { getAllFiles, validateChars } from './tools/validateAsciiChars.js' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const SRC_DIR = path.join(__dirname, '/src') const BUNDLE_ENTRY = `${SRC_DIR}/defaultInstance.js` const HEADER = `${SRC_DIR}/header.js` const VERSION = `${SRC_DIR}/version.js` const COMPILE_SRC = `${SRC_DIR}/**/*.?(c)js` const COMPILE_ENTRY_SRC = `${SRC_DIR}/entry/**/*.js` const COMPILE_DIR = path.join(__dirname, '/lib') const COMPILE_BROWSER = `${COMPILE_DIR}/browser` const COMPILE_CJS = `${COMPILE_DIR}/cjs` const COMPILE_ESM = `${COMPILE_DIR}/esm` // es modules const COMPILE_ENTRY_LIB = `${COMPILE_CJS}/entry` const FILE = 'math.js' const REF_SRC = SRC_DIR + '/' const REF_DIR = path.join(__dirname, '/docs') const REF_DEST = `${REF_DIR}/reference/functions` const REF_ROOT = `${REF_DIR}/reference` const MATH_JS = `${COMPILE_BROWSER}/${FILE}` const COMPILED_HEADER = `${COMPILE_CJS}/header.js` const PACKAGE_JSON_COMMONJS = '{\n "type": "commonjs"\n}\n' const AUTOGENERATED_WARNING = ` // Note: This file is automatically generated when building math.js. // Changes made in this file will be overwritten. ` // read the version number from package.json function getVersion () { return JSON.parse(String(fs.readFileSync('./package.json'))).version } // generate banner with today's date and correct version function createBanner () { const today = new Date().toISOString().substr(0, 10) // today, formatted as yyyy-mm-dd const version = getVersion() return String(fs.readFileSync(HEADER)) .replace('@@date', today) .replace('@@version', version) } // generate a js file containing the version number function updateVersionFile (done) { const version = getVersion() fs.writeFileSync(VERSION, `export const version = '${version}'${AUTOGENERATED_WARNING}`) done() } const bannerPlugin = new webpack.BannerPlugin({ banner: createBanner(), entryOnly: true, raw: true }) const babelConfig = JSON.parse(String(fs.readFileSync('./.babelrc'))) const webpackConfig = { entry: BUNDLE_ENTRY, mode: 'production', performance: { hints: false }, // to hide the "asset size limit" warning output: { library: 'math', libraryTarget: 'umd', libraryExport: 'default', path: COMPILE_BROWSER, globalObject: 'this', filename: FILE }, node: false, // to make sure Webpack doesn't generate 'new Function("return this")' in the bundle output, see https://github.com/josdejong/mathjs/issues/3001 plugins: [ bannerPlugin // new webpack.optimize.ModuleConcatenationPlugin() // TODO: ModuleConcatenationPlugin seems not to work. https://medium.com/webpack/webpack-3-official-release-15fd2dd8f07b ], module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: babelConfig } } ] }, devtool: 'source-map', cache: true } // create a single instance of the compiler to allow caching const compiler = webpack(webpackConfig) function bundle (done) { // update the banner contents (has a date in it which should stay up to date) bannerPlugin.banner = createBanner() compiler.run(function (err, stats) { if (err) { log(err) done(err) } const info = stats.toJson() if (stats.hasWarnings()) { log('Webpack warnings:\n' + info.warnings.join('\n')) } if (stats.hasErrors()) { log('Webpack errors:\n' + info.errors.join('\n')) done(new Error('Compile failed')) } // create commonjs package.json file fs.writeFileSync(path.join(COMPILE_BROWSER, 'package.json'), PACKAGE_JSON_COMMONJS) log(`bundled ${MATH_JS}`) done() }) } function compileCommonJs () { // create a package.json file in the commonjs folder mkdirp.sync(COMPILE_CJS) fs.writeFileSync(path.join(COMPILE_CJS, 'package.json'), PACKAGE_JSON_COMMONJS) return gulp.src(COMPILE_SRC) .pipe(babel()) .pipe(gulp.dest(COMPILE_CJS)) } function compileESModules () { return gulp.src(COMPILE_SRC) .pipe(babel({ ...babelConfig, presets: [ ['@babel/preset-env', { modules: false, targets: { esmodules: true } }] ] })) .pipe(gulp.dest(COMPILE_ESM)) } function compileEntryFiles () { return gulp.src(COMPILE_ENTRY_SRC) .pipe(babel()) .pipe(gulp.dest(COMPILE_ENTRY_LIB)) } function writeCompiledHeader (cb) { fs.writeFileSync(COMPILED_HEADER, createBanner()) cb() } function validateAscii (done) { const Reset = '\x1b[0m' const BgRed = '\x1b[41m' getAllFiles(SRC_DIR) .map(validateChars) .forEach(function (invalidChars) { invalidChars.forEach(function (res) { console.log(res.insideComment ? '' : BgRed, 'file:', res.filename, 'ln:' + res.ln, 'col:' + res.col, 'inside comment:', res.insideComment, 'code:', res.c, 'character:', String.fromCharCode(res.c), Reset ) }) }) done() } async function generateDocs (done) { const all = (await import('file://' + REF_SRC + 'defaultInstance.js')).default const functionNames = Object.keys(all) .filter(key => typeof all[key] === 'function') if (functionNames.length === 0) { throw new Error('No function names found, is the doc generator broken?') } cleanup(REF_DEST, REF_ROOT) iteratePath(functionNames, REF_SRC, REF_DEST, REF_ROOT) done() } function generateEntryFilesCallback (done) { generateEntryFiles().then(() => { done() }) } /** * Remove generated files * * @returns {Promise | *} */ async function clean () { await deleteAsync([ // legacy compiled files './es/', // generated browser bundle, esm code, and commonjs code './lib/', // generated source files 'src/**/*.generated.js' ]) } gulp.task('browser', bundle) gulp.task('clean', clean) gulp.task('docs', generateDocs) // check whether any of the source files contains non-ascii characters gulp.task('validate:ascii', validateAscii) // The watch task (to automatically rebuild when the source code changes) gulp.task('watch', function watch () { const files = ['package.json', 'src/**/*.js'] const options = { // ignore version.js else we get an infinite loop since it's updated during bundle ignored: /version\.js/, ignoreInitial: false, delay: 100 } gulp.watch(files, options, gulp.parallel(bundle, compileCommonJs)) }) // The default task (called when you run `gulp`) gulp.task('default', gulp.series( clean, updateVersionFile, generateEntryFilesCallback, compileCommonJs, compileEntryFiles, compileESModules, // Must be after generateEntryFilesCallback writeCompiledHeader, bundle, generateDocs ))