diff --git a/package.json b/package.json index b7e63074f..74331954e 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "version": "1.2.2", "main": "dist/_30s.js", "module": "dist/_30s.esm.js", + "sideEffects": false, "scripts": { "glossary:librarian": "node ./scripts/glossary/library.js", "glossary:keymaker": "node ./scripts/glossary/keyword.js", diff --git a/scripts/module.js b/scripts/module.js index b5260b57d..cc39fce2f 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -1,7 +1,7 @@ -/* - Builds the `_30s` module. -*/ -// Load modules +/** + * Builds the `_30s` module in UMD and ESM formats. + * Also builds the test module file for testing snippets. + */ const fs = require('fs-extra'); const path = require('path'); const chalk = require('chalk'); @@ -9,110 +9,159 @@ const util = require('./util'); const { rollup } = require('rollup'); const babel = require('rollup-plugin-babel'); const minify = require('rollup-plugin-babel-minify'); -// Set variables for paths + +const MODULE_NAME = '_30s'; const SNIPPETS_PATH = './snippets'; const SNIPPETS_ARCHIVE_PATH = './snippets_archive'; -const IMPORTS = './imports.js'; -const TEST_PACKAGE = './test/_30s.js'; -const MODULE_NAME = '_30s'; -const DIST = './dist'; -// Regex for selecting code blocks -const codeRE = /```\s*js([\s\S]*?)```/; -// Read snippets, build packages -(async() => { - // Start the timer of the script +const DIST_PATH = './dist'; +const ROLLUP_INPUT_FILE = './imports.temp.js'; +const TEST_MODULE_FILE = './test/_30s.js'; +const CODE_RE = /```\s*js([\s\S]*?)```/; + +/** + * Returns the raw markdown string. + */ +function getRawSnippetString(snippetPath, snippet) { + return fs.readFileSync(path.join(snippetPath, snippet), 'utf8'); +} + +/** + * Returns the JavaScript code from the raw markdown string. + */ +function getCode(rawSnippetString) { + return rawSnippetString.match(CODE_RE)[1].replace('\n', ''); +} + +/** + * Builds the UMD + ESM files to the ./dist directory. + */ +async function doRollup() { + // Plugins + const es5 = babel({ presets: ['@babel/preset-env'] }); + const min = minify({ comments: false }); + + const output = format => file => ({ + format, + file, + name: MODULE_NAME + }); + + const umd = output('umd'); + const esm = output('es'); + + const bundle = await rollup({ input: ROLLUP_INPUT_FILE }); + const bundleES5 = await rollup({ input: ROLLUP_INPUT_FILE, plugins: [es5] }); + const bundleES5Min = await rollup({ + input: ROLLUP_INPUT_FILE, + plugins: [es5, min] + }); + + const baseName = `${DIST_PATH}/${MODULE_NAME}`; + + // UMD ES2018 + await bundle.write(umd(`${baseName}.js`)); + // ESM ES2018 + await bundle.write(esm(`${baseName}.esm.js`)); + // UMD ES5 + await bundleES5.write(umd(`${baseName}.es5.js`)); + // UMD ES5 min + await bundleES5Min.write(umd(`${baseName}.es5.min.js`)); +} + +/** + * Starts the build process. + */ +async function build() { console.time('Packager'); + + let requires = []; + let esmExportString = ''; + let cjsExportString = ''; + try { - const tagDatabase = fs.readFileSync('tag_database', 'utf8'); - const nodeSnippets = tagDatabase.split('\n').filter(v => v.search(/:.*node/g) !== -1).map(v => v.slice(0, v.indexOf(':'))); + if (!fs.existsSync(DIST_PATH)) fs.mkdirSync(DIST_PATH); + fs.writeFileSync(ROLLUP_INPUT_FILE, ''); + fs.writeFileSync(TEST_MODULE_FILE, ''); + + // All the snippets that are Node.js-based and will break in a browser + // environment + const nodeSnippets = fs + .readFileSync('tag_database', 'utf8') + .split('\n') + .filter(v => v.search(/:.*node/g) !== -1) + .map(v => v.slice(0, v.indexOf(':'))); + const snippets = fs.readdirSync(SNIPPETS_PATH); - const snippetExports = `module.exports = {${snippets.map(v => v.replace('.md', '')).join(',')}}`; - let requires = []; - let importData = ''; - const archivedSnippets = fs.readdirSync(SNIPPETS_ARCHIVE_PATH).filter(v => v !== 'README.md'); - const testExports = `module.exports = {${[...snippets, ...archivedSnippets].map(v => v.replace('.md', '')).join(',')}}`; - // Create `temp` and `dist` folders if they don't already exist. - if (!fs.existsSync(DIST)) fs.mkdirSync(DIST); - // Write `imports.js` - fs.writeFileSync(IMPORTS, ''); - fs.writeFileSync(TEST_PACKAGE, ''); + const archivedSnippets = fs + .readdirSync(SNIPPETS_ARCHIVE_PATH) + .filter(v => v !== 'README.md'); snippets.forEach(snippet => { - const snippetData = fs.readFileSync(path.join(SNIPPETS_PATH, snippet), 'utf8'); + const rawSnippetString = getRawSnippetString(SNIPPETS_PATH, snippet); const snippetName = snippet.replace('.md', ''); - let code = snippetData.match(codeRE)[1].replace('\n', ''); + let code = getCode(rawSnippetString); if (nodeSnippets.includes(snippetName)) { requires.push(code.match(/const.*=.*require\(([^\)]*)\);/g)); code = code.replace(/const.*=.*require\(([^\)]*)\);/g, ''); } - importData += code; + esmExportString += `export ${code}`; + cjsExportString += code; }); - // Write the data to the imports file - requires = [...new Set(requires.filter(Boolean).map(v => v[0].replace('require(', 'typeof require !== "undefined" && require(')))].join('\n'); - fs.writeFileSync(IMPORTS, `${requires}\n\n${importData}\n\n${snippetExports}`); - archivedSnippets.forEach(snippet => { - const snippetData = fs.readFileSync(path.join(SNIPPETS_ARCHIVE_PATH, snippet), 'utf8'); - let code = snippetData.match(codeRE)[1].replace('\n', ''); - importData += code; + const rawSnippetString = getRawSnippetString( + SNIPPETS_ARCHIVE_PATH, + snippet + ); + cjsExportString += getCode(rawSnippetString); }); - fs.writeFileSync(TEST_PACKAGE, `${requires}\n\n${importData}\n\n${testExports}`); + + requires = [ + ...new Set( + requires + .filter(Boolean) + .map(v => + v[0].replace( + 'require(', + 'typeof require !== "undefined" && require(' + ) + ) + ) + ].join('\n'); + + fs.writeFileSync(ROLLUP_INPUT_FILE, `${requires}\n\n${esmExportString}`); + + const testExports = `module.exports = {${[...snippets, ...archivedSnippets] + .map(v => v.replace('.md', '')) + .join(',')}}`; + + fs.writeFileSync( + TEST_MODULE_FILE, + `${requires}\n\n${cjsExportString}\n\n${testExports}` + ); // Check Travis builds - Will skip builds on Travis if not CRON/API if (util.isTravisCI() && util.isNotTravisCronOrAPI()) { - fs.unlink(IMPORTS); + fs.unlink(ROLLUP_INPUT_FILE); console.log( - `${chalk.green('NOBUILD')} Module build terminated, not a cron job or a custom build!` + `${chalk.green( + 'NOBUILD' + )} Module build terminated, not a cron job or a custom build!` ); console.timeEnd('Packager'); process.exit(0); } - // Write to the proper files and start the `rollup` script - const es5 = babel({ - presets: ['@babel/preset-env'] - }); - const min = minify({ comments: false }); - const bundle = await rollup({ input: IMPORTS }); - // UMD ES2018 - await bundle.write({ - file: `${DIST}/${MODULE_NAME}.js`, - name: MODULE_NAME, - format: 'umd' - }); - // ESM ES2018 - await bundle.write({ - file: `${DIST}/${MODULE_NAME}.esm.js`, - name: MODULE_NAME, - format: 'es' - }); - // UMD ES5 - const bundleES5 = await rollup({ input: IMPORTS, plugins: [es5] }); - await bundleES5.write({ - file: `${DIST}/${MODULE_NAME}.es5.js`, - name: MODULE_NAME, - format: 'umd' - }); - // UMD ES5 min - const bundleES5Min = await rollup({ - input: IMPORTS, - plugins: [es5, min] - }); - await bundleES5Min.write({ - file: `${DIST}/${MODULE_NAME}.es5.min.js`, - name: MODULE_NAME, - format: 'umd' - }); + await doRollup(); + + // Clean up the temporary input file Rollup used for building the module + fs.unlink(ROLLUP_INPUT_FILE); - // Clean up temporary data - fs.unlink(IMPORTS); - // Log a success message console.log(`${chalk.green('SUCCESS!')} Snippet module built!`); - // Log the time taken console.timeEnd('Packager'); } catch (err) { - // Handle errors (hopefully not!) console.log(`${chalk.red('ERROR!')} During module creation: ${err}`); process.exit(1); } -})(); +} + +build();