js-build-tools 5.3.1 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -444,7 +444,9 @@ module.exports = babelConfig
444
444
  the readme it generates. Instead, this can generate HTML documentation straight from the TypeScript source with
445
445
  [TypeDoc](https://typedoc.org/). Every folder in your source directory becomes a module of the documentation, the default
446
446
  export of each file (one function per file) is listed under the name of the file, and everything else a file exports (for
447
- example the types in a `types.ts`) is listed as it is. Index files, tests and declaration files are left out. The
447
+ example the types in a `types.ts`) is listed as it is. Index files, tests and declaration files are left out. A file
448
+ which sits directly in the source directory, beside the folders, is a module of its own (unless it is `index` or
449
+ `main`). The first comment of a folder's `index.ts` (or the header comment of such a file) describes its module. The
448
450
  comments on your code are the documentation, and the types come from the code itself, so `@module`, `@memberOf`,
449
451
  `@typedef` and the `{Type}` of `@param` are not needed.
450
452
 
package/dist/build.js CHANGED
@@ -1,68 +1,68 @@
1
- 'use strict'
2
-
3
- require('core-js/modules/esnext.weak-map.delete-all.js')
4
- Object.defineProperty(exports, '__esModule', {
5
- value: true
6
- })
7
- exports.build = void 0
8
- const _bundle = require('./bundle.js')
9
- const _bundleLint = require('./bundleLint.js')
10
- const _bundleMinify = require('./bundleMinify.js')
11
- const _partials = require('./partials.js')
12
- const _compileReadme = require('./compileReadme.js')
13
- const _typeDocs = require('./typeDocs.js')
14
- const _distLint = require('./distLint.js')
15
- const _distMinify = require('./distMinify.js')
16
- const _gulp = require('gulp')
17
- const gulpConfig = _interopRequireWildcard(require('../gulp.config.js'))
18
- const _copyFonts = require('./copyFonts.js')
19
- const _images = require('./images.js')
20
- const _sass = require('./sass.js')
21
- const _testFull = require('./testFull.js')
22
- function _interopRequireWildcard (e, t) { if (typeof WeakMap === 'function') var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; let o; let i; const f = { __proto__: null, default: e }; if (e === null || typeof e !== 'object' && typeof e !== 'function') return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f) } for (const t in e) t !== 'default' && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f })(e, t) }
1
+ 'use strict'
2
+
3
+ require('core-js/modules/esnext.weak-map.delete-all.js')
4
+ Object.defineProperty(exports, '__esModule', {
5
+ value: true
6
+ })
7
+ exports.build = void 0
8
+ const _bundle = require('./bundle.js')
9
+ const _bundleLint = require('./bundleLint.js')
10
+ const _bundleMinify = require('./bundleMinify.js')
11
+ const _partials = require('./partials.js')
12
+ const _compileReadme = require('./compileReadme.js')
13
+ const _typeDocs = require('./typeDocs.js')
14
+ const _distLint = require('./distLint.js')
15
+ const _distMinify = require('./distMinify.js')
16
+ const _gulp = require('gulp')
17
+ const gulpConfig = _interopRequireWildcard(require('../gulp.config.js'))
18
+ const _copyFonts = require('./copyFonts.js')
19
+ const _images = require('./images.js')
20
+ const _sass = require('./sass.js')
21
+ const _testFull = require('./testFull.js')
22
+ function _interopRequireWildcard (e, t) { if (typeof WeakMap === 'function') var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; let o; let i; const f = { __proto__: null, default: e }; if (e === null || typeof e !== 'object' && typeof e !== 'function') return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f) } for (const t in e) t !== 'default' && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f })(e, t) }
23
23
  /**
24
24
  * Runs several processes to build and validate the project.
25
25
  * Cleans, distributes (lint and minify), bundles (lint and minify), creates the readme, then runs the tests.
26
26
  * @memberOf module:js-build-tools
27
27
  * @returns {stream.Stream}
28
- */
29
- const build = (done = null) => {
30
- const distLintMinify = (0, _gulp.parallel)(_distLint.distLint, _distMinify.distMinify)
31
- const bundleLintMinify = (0, _gulp.parallel)(_bundleLint.bundleLint, _bundleMinify.bundleMinify)
32
- const buildActions = [(0, _partials.distSeries)(), distLintMinify]
33
- const generateDocs = gulpConfig.get('docs.enabled')
34
- if (generateDocs) {
35
- // The documentation is generated from the TypeScript source itself, the readme is written by hand
36
- buildActions.push(_typeDocs.typeDocs)
37
- } else if (gulpConfig.get('typescript.enabled')) {
38
- // For ts usage, we need to run the readme on the dist directly since that is where the .js files are located
39
- buildActions.push(_compileReadme.compileReadme)
40
- }
41
- if (gulpConfig.get('browser.enabled')) {
42
- // If not 'nodeOnly' then we also want to bundle for browser after we complete the dist directory
43
- buildActions.push(_bundle.bundle)
44
- buildActions.push(bundleLintMinify)
45
- }
46
- const runActions = [(0, _gulp.series)(...buildActions)]
47
- if (!generateDocs && !gulpConfig.get('typescript.enabled')) {
48
- // Since we didn't run this in series after dist because of typescript, we need to run it now. Potentially faster here.
49
- runActions.push(_compileReadme.compileReadme)
50
- }
51
- runActions.push(_testFull.testFull)
52
- if (gulpConfig.get('fonts.enabled')) {
53
- // Conditionally add Fonts process
54
- runActions.push(_copyFonts.copyFonts)
55
- }
56
- if (gulpConfig.get('images.enabled')) {
57
- // Conditionally add Images process
58
- runActions.push(_images.images)
59
- }
60
- if (gulpConfig.get('sass.enabled')) {
61
- // Conditionally add SASS process
62
- runActions.push(_sass.sass)
63
- }
64
- // clean has to finish before anything writes into the output folders - images, fonts and sass all write into
65
- // browser/, so running clean in parallel with them races (ENOTEMPTY errors from clean, or output deleted mid-write).
66
- return (0, _gulp.series)(_partials.clean, (0, _gulp.parallel)(...runActions))(done)
67
- }
68
- exports.build = build
28
+ */
29
+ const build = (done = null) => {
30
+ const distLintMinify = (0, _gulp.parallel)(_distLint.distLint, _distMinify.distMinify)
31
+ const bundleLintMinify = (0, _gulp.parallel)(_bundleLint.bundleLint, _bundleMinify.bundleMinify)
32
+ const buildActions = [(0, _partials.distSeries)(), distLintMinify]
33
+ const generateDocs = gulpConfig.get('docs.enabled')
34
+ if (generateDocs) {
35
+ // The documentation is generated from the TypeScript source itself, the readme is written by hand
36
+ buildActions.push(_typeDocs.typeDocs)
37
+ } else if (gulpConfig.get('typescript.enabled')) {
38
+ // For ts usage, we need to run the readme on the dist directly since that is where the .js files are located
39
+ buildActions.push(_compileReadme.compileReadme)
40
+ }
41
+ if (gulpConfig.get('browser.enabled')) {
42
+ // If not 'nodeOnly' then we also want to bundle for browser after we complete the dist directory
43
+ buildActions.push(_bundle.bundle)
44
+ buildActions.push(bundleLintMinify)
45
+ }
46
+ const runActions = [(0, _gulp.series)(...buildActions)]
47
+ if (!generateDocs && !gulpConfig.get('typescript.enabled')) {
48
+ // Since we didn't run this in series after dist because of typescript, we need to run it now. Potentially faster here.
49
+ runActions.push(_compileReadme.compileReadme)
50
+ }
51
+ runActions.push(_testFull.testFull)
52
+ if (gulpConfig.get('fonts.enabled')) {
53
+ // Conditionally add Fonts process
54
+ runActions.push(_copyFonts.copyFonts)
55
+ }
56
+ if (gulpConfig.get('images.enabled')) {
57
+ // Conditionally add Images process
58
+ runActions.push(_images.images)
59
+ }
60
+ if (gulpConfig.get('sass.enabled')) {
61
+ // Conditionally add SASS process
62
+ runActions.push(_sass.sass)
63
+ }
64
+ // clean has to finish before anything writes into the output folders - images, fonts and sass all write into
65
+ // browser/, so running clean in parallel with them races (ENOTEMPTY errors from clean, or output deleted mid-write).
66
+ return (0, _gulp.series)(_partials.clean, (0, _gulp.parallel)(...runActions))(done)
67
+ }
68
+ exports.build = build
package/dist/bundle.js CHANGED
@@ -1,36 +1,43 @@
1
- 'use strict'
2
-
3
- require('core-js/modules/esnext.weak-map.delete-all.js')
4
- Object.defineProperty(exports, '__esModule', {
5
- value: true
6
- })
7
- exports.bundle = void 0
8
- const _browserify = _interopRequireDefault(require('browserify'))
9
- const _gulp = require('gulp')
10
- const gulpConfig = _interopRequireWildcard(require('../gulp.config.js'))
11
- const _vinylSourceStream = _interopRequireDefault(require('vinyl-source-stream'))
12
- function _interopRequireWildcard (e, t) { if (typeof WeakMap === 'function') var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; let o; let i; const f = { __proto__: null, default: e }; if (e === null || typeof e !== 'object' && typeof e !== 'function') return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f) } for (const t in e) t !== 'default' && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f })(e, t) }
13
- function _interopRequireDefault (e) { return e && e.__esModule ? e : { default: e } }
1
+ 'use strict'
2
+
3
+ require('core-js/modules/esnext.weak-map.delete-all.js')
4
+ Object.defineProperty(exports, '__esModule', {
5
+ value: true
6
+ })
7
+ exports.bundle = void 0
8
+ const _browserify = _interopRequireDefault(require('browserify'))
9
+ const _gulp = require('gulp')
10
+ const gulpConfig = _interopRequireWildcard(require('../gulp.config.js'))
11
+ const _vinylSourceStream = _interopRequireDefault(require('vinyl-source-stream'))
12
+ function _interopRequireWildcard (e, t) { if (typeof WeakMap === 'function') var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; let o; let i; const f = { __proto__: null, default: e }; if (e === null || typeof e !== 'object' && typeof e !== 'function') return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f) } for (const t in e) t !== 'default' && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f })(e, t) }
13
+ function _interopRequireDefault (e) { return e && e.__esModule ? e : { default: e } }
14
14
  /**
15
15
  * Starting at the distribution entry point, bundle all the files into a single file and store them in the specified output directory.
16
16
  *
17
+ * browser.name does double duty: it names the output file (browser.name.js) and, via browserify's `standalone`
18
+ * option, is also the name the bundle's exports are exposed under - as a global when loaded with a bare <script>
19
+ * tag, or as the module when required via CommonJS/AMD. Without this, a bundle built from a real entry point (one
20
+ * that actually exports something) builds successfully but exposes nothing a consumer can reach.
21
+ *
17
22
  * Two escape hatches from browser.config.json keep an unwanted dependency (or a dependency's dependency) out of the
18
23
  * bundle: 'ignore' replaces it with an empty object (safe when your code never actually calls into it - for example
19
24
  * a library's default option value you always override with your own) and 'exclude' leaves it out entirely (your
20
25
  * code must not require it, or provide it another way, such as a separate script tag).
21
26
  * @memberOf module:js-build-tools
22
27
  * @returns {stream.Stream}
23
- */
24
- const bundle = () => {
25
- const bundler = (0, _browserify.default)(gulpConfig.get('dist.main'))
26
- const ignore = gulpConfig.get('browser.ignore')
27
- const exclude = gulpConfig.get('browser.exclude')
28
- if (ignore && ignore.length) {
29
- bundler.ignore(ignore)
30
- }
31
- if (exclude && exclude.length) {
32
- bundler.exclude(exclude)
33
- }
34
- return bundler.bundle().pipe((0, _vinylSourceStream.default)(`${gulpConfig.get('browser.name')}.js`)).pipe((0, _gulp.dest)(gulpConfig.get('browser.to')))
35
- }
36
- exports.bundle = bundle
28
+ */
29
+ const bundle = () => {
30
+ const bundler = (0, _browserify.default)(gulpConfig.get('dist.main'), {
31
+ standalone: gulpConfig.get('browser.name')
32
+ })
33
+ const ignore = gulpConfig.get('browser.ignore')
34
+ const exclude = gulpConfig.get('browser.exclude')
35
+ if (ignore && ignore.length) {
36
+ bundler.ignore(ignore)
37
+ }
38
+ if (exclude && exclude.length) {
39
+ bundler.exclude(exclude)
40
+ }
41
+ return bundler.bundle().pipe((0, _vinylSourceStream.default)(`${gulpConfig.get('browser.name')}.js`)).pipe((0, _gulp.dest)(gulpConfig.get('browser.to')))
42
+ }
43
+ exports.bundle = bundle
@@ -1 +1 @@
1
- "use strict";require("core-js/modules/esnext.weak-map.delete-all.js"),Object.defineProperty(exports,"__esModule",{value:!0}),exports.bundle=void 0;var _browserify=_interopRequireDefault(require("browserify")),_gulp=require("gulp"),gulpConfig=_interopRequireWildcard(require("../gulp.config.js")),_vinylSourceStream=_interopRequireDefault(require("vinyl-source-stream"));function _interopRequireWildcard(e,r){if("function"==typeof WeakMap)var t=new WeakMap,u=new WeakMap;return(_interopRequireWildcard=function(e,r){if(!r&&e&&e.__esModule)return e;var n,i,o={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return o;if(n=r?u:t){if(n.has(e))return n.get(e);n.set(e,o)}for(const r in e)"default"!==r&&{}.hasOwnProperty.call(e,r)&&((i=(n=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,r))&&(i.get||i.set)?n(o,r,i):o[r]=e[r]);return o})(e,r)}function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const bundle=()=>{const e=(0,_browserify.default)(gulpConfig.get("dist.main")),r=gulpConfig.get("browser.ignore"),t=gulpConfig.get("browser.exclude");return r&&r.length&&e.ignore(r),t&&t.length&&e.exclude(t),e.bundle().pipe((0,_vinylSourceStream.default)(`${gulpConfig.get("browser.name")}.js`)).pipe((0,_gulp.dest)(gulpConfig.get("browser.to")))};exports.bundle=bundle;
1
+ "use strict";require("core-js/modules/esnext.weak-map.delete-all.js"),Object.defineProperty(exports,"__esModule",{value:!0}),exports.bundle=void 0;var _browserify=_interopRequireDefault(require("browserify")),_gulp=require("gulp"),gulpConfig=_interopRequireWildcard(require("../gulp.config.js")),_vinylSourceStream=_interopRequireDefault(require("vinyl-source-stream"));function _interopRequireWildcard(e,r){if("function"==typeof WeakMap)var t=new WeakMap,n=new WeakMap;return(_interopRequireWildcard=function(e,r){if(!r&&e&&e.__esModule)return e;var u,o,i={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return i;if(u=r?n:t){if(u.has(e))return u.get(e);u.set(e,i)}for(const r in e)"default"!==r&&{}.hasOwnProperty.call(e,r)&&((o=(u=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,r))&&(o.get||o.set)?u(i,r,o):i[r]=e[r]);return i})(e,r)}function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const bundle=()=>{const e=(0,_browserify.default)(gulpConfig.get("dist.main"),{standalone:gulpConfig.get("browser.name")}),r=gulpConfig.get("browser.ignore"),t=gulpConfig.get("browser.exclude");return r&&r.length&&e.ignore(r),t&&t.length&&e.exclude(t),e.bundle().pipe((0,_vinylSourceStream.default)(`${gulpConfig.get("browser.name")}.js`)).pipe((0,_gulp.dest)(gulpConfig.get("browser.to")))};exports.bundle=bundle;
@@ -4,7 +4,7 @@ require("core-js/modules/esnext.weak-map.delete-all.js");
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.writeDocEntries = exports.typeDocsFor = exports.loadTypeDoc = exports.describeFolder = exports.describeExports = void 0;
7
+ exports.writeDocEntries = exports.typeDocsFor = exports.loadTypeDoc = exports.describeFolder = exports.describeFile = exports.describeExports = void 0;
8
8
  require("core-js/modules/es.json.stringify.js");
9
9
  require("core-js/modules/esnext.iterator.constructor.js");
10
10
  require("core-js/modules/esnext.iterator.filter.js");
@@ -102,7 +102,20 @@ const describeExports = (ts, filePath) => {
102
102
  exports.describeExports = describeExports;
103
103
  const describeFolder = folderPath => {
104
104
  const indexFile = ['index.ts', 'index.tsx', 'index.mts', 'index.cts'].map(name => _nodePath.default.join(folderPath, name)).find(file => _nodeFs.default.existsSync(file));
105
- const comment = indexFile && _nodeFs.default.readFileSync(indexFile, 'utf8').match(/^\s*\/\*\*([\s\S]*?)\*\//);
105
+ return indexFile ? describeFile(indexFile) : '';
106
+ };
107
+
108
+ /**
109
+ * The description of a source file for its module: its first doc comment without the tags (@module, @file, ...), when
110
+ * that comment is a header (it is followed by an import or export, so it does not document a declaration itself), or
111
+ * an empty string.
112
+ * @memberOf module:partials
113
+ * @param {string} filePath
114
+ * @returns {string}
115
+ */
116
+ exports.describeFolder = describeFolder;
117
+ const describeFile = filePath => {
118
+ const comment = _nodeFs.default.readFileSync(filePath, 'utf8').match(/^\s*\/\*\*([\s\S]*?)\*\/\s*(import\b|export\s+(\*|\{[^}]*\}\s+from)|$)/);
106
119
  if (!comment) {
107
120
  return '';
108
121
  }
@@ -116,14 +129,16 @@ const describeFolder = folderPath => {
116
129
  * entry file, so the modules of the documentation mirror the folders. The default export of each source file is
117
130
  * re-exported under the name of the file (the source files hold one function each), and anything else a file exports
118
131
  * is re-exported as it is (that is where the types.ts files go). Index files (the barrels), tests and declaration
119
- * files are left out, but the first doc comment of a folder's index file becomes the description of its module. A source directory with no folders gets a single entry called index.
132
+ * files are left out, but the first doc comment of a folder's index file becomes the description of its module.
133
+ * A file which sits beside the folders (not an index or main file) is a module of its own, described by its header
134
+ * comment. A source directory with no folders gets a single entry called index.
120
135
  * @memberOf module:partials
121
136
  * @param {Object} ts - The TypeScript compiler API.
122
137
  * @param {string} srcDir - The directory holding the TypeScript source.
123
138
  * @param {string} entryDir - The directory to write the entry files into.
124
139
  * @returns {Array<string>} The paths of the entry files.
125
140
  */
126
- exports.describeFolder = describeFolder;
141
+ exports.describeFile = describeFile;
127
142
  const writeDocEntries = (ts, srcDir, entryDir) => {
128
143
  const absoluteSrc = _nodePath.default.resolve(srcDir);
129
144
  _nodeFs.default.mkdirSync(entryDir, {
@@ -136,10 +151,19 @@ const writeDocEntries = (ts, srcDir, entryDir) => {
136
151
  path: _nodePath.default.join(absoluteSrc, entry.name),
137
152
  files: listSources(_nodePath.default.join(absoluteSrc, entry.name))
138
153
  })).filter(folder => folder.files.length);
139
- const groups = folders.length ? folders : [{
154
+ const rootFiles = _nodeFs.default.readdirSync(absoluteSrc, {
155
+ withFileTypes: true
156
+ }).filter(entry => entry.isFile() && isSource(entry.name) && !/^(index|main)\./.test(entry.name)).map(entry => _nodePath.default.join(absoluteSrc, entry.name));
157
+ // Folders make modules, and so do the files which sit beside them (except the barrel: index and main). A source
158
+ // directory with no folders gets a single module called index which holds its files.
159
+ const groups = folders.length ? [...folders, ...rootFiles.map(file => ({
160
+ name: toIdentifier(_nodePath.default.basename(file).replace(/\.[cm]?tsx?$/, '')),
161
+ files: [file],
162
+ description: describeFile(file)
163
+ })).filter(group => !folders.some(folder => folder.name === group.name))] : [{
140
164
  name: 'index',
141
165
  path: absoluteSrc,
142
- files: listSources(absoluteSrc).filter(file => !/^(index|main)\./.test(_nodePath.default.basename(file)))
166
+ files: rootFiles
143
167
  }];
144
168
  return groups.map(group => {
145
169
  const lines = group.files.filter(file => !/^index\./.test(_nodePath.default.basename(file))).sort().flatMap(file => {
@@ -151,7 +175,7 @@ const writeDocEntries = (ts, srcDir, entryDir) => {
151
175
  const fileName = _nodePath.default.basename(file).replace(/\.[cm]?tsx?$/, '');
152
176
  return [...(hasDefault ? [`export { default as ${toIdentifier(fileName)} } from '${modulePath}'`] : []), ...(hasNamed ? [`export * from '${modulePath}'`] : [])];
153
177
  });
154
- const description = group.path ? describeFolder(group.path) : '';
178
+ const description = group.description ?? (group.path ? describeFolder(group.path) : '');
155
179
  // TypeDoc takes the first comment of an entry file which has the @module tag as the description of the module
156
180
  const moduleComment = description ? `/**\n${description.split('\n').map(line => ` * ${line}`.trimEnd()).join('\n')}\n * @module\n */\n\n` : '';
157
181
  const entryPath = _nodePath.default.join(entryDir, `${group.name}.ts`);
package/dist/typeDocs.js CHANGED
@@ -1,14 +1,14 @@
1
- 'use strict'
2
-
3
- Object.defineProperty(exports, '__esModule', {
4
- value: true
5
- })
6
- exports.typeDocs = void 0
7
- const _typeDocsFor = require('./partials/typeDocsFor.js')
1
+ 'use strict'
2
+
3
+ Object.defineProperty(exports, '__esModule', {
4
+ value: true
5
+ })
6
+ exports.typeDocs = void 0
7
+ const _typeDocsFor = require('./partials/typeDocsFor.js')
8
8
  /**
9
9
  * Generate the HTML documentation of a TypeScript project from its source, with the modules mirroring the source
10
10
  * folders. Turn it on with docs.enabled, see {@link module:partials.typeDocsFor}.
11
11
  * @memberOf module:js-build-tools
12
12
  * @returns {Function}
13
- */
14
- const typeDocs = exports.typeDocs = (0, _typeDocsFor.typeDocsFor)()
13
+ */
14
+ const typeDocs = exports.typeDocs = (0, _typeDocsFor.typeDocsFor)()
@@ -6,6 +6,11 @@ import source from 'vinyl-source-stream'
6
6
  /**
7
7
  * Starting at the distribution entry point, bundle all the files into a single file and store them in the specified output directory.
8
8
  *
9
+ * browser.name does double duty: it names the output file (browser.name.js) and, via browserify's `standalone`
10
+ * option, is also the name the bundle's exports are exposed under - as a global when loaded with a bare <script>
11
+ * tag, or as the module when required via CommonJS/AMD. Without this, a bundle built from a real entry point (one
12
+ * that actually exports something) builds successfully but exposes nothing a consumer can reach.
13
+ *
9
14
  * Two escape hatches from browser.config.json keep an unwanted dependency (or a dependency's dependency) out of the
10
15
  * bundle: 'ignore' replaces it with an empty object (safe when your code never actually calls into it - for example
11
16
  * a library's default option value you always override with your own) and 'exclude' leaves it out entirely (your
@@ -14,7 +19,7 @@ import source from 'vinyl-source-stream'
14
19
  * @returns {stream.Stream}
15
20
  */
16
21
  export const bundle = () => {
17
- const bundler = browserify(gulpConfig.get('dist.main'))
22
+ const bundler = browserify(gulpConfig.get('dist.main'), { standalone: gulpConfig.get('browser.name') })
18
23
  const ignore = gulpConfig.get('browser.ignore')
19
24
  const exclude = gulpConfig.get('browser.exclude')
20
25
  if (ignore && ignore.length) {
@@ -1,4 +1,5 @@
1
1
  import fs from 'fs'
2
+ import path from 'path'
2
3
  import * as setUp from './test-helpers/setUp.mjs'
3
4
  import { bundle } from './bundle.mjs'
4
5
  import { countMatches } from 'test-filesystem'
@@ -66,6 +67,25 @@ describe('bundle', () => {
66
67
  done()
67
68
  })
68
69
  })
70
+
71
+ test('exposes browser.name as the standalone export, reachable by a real consumer', done => {
72
+ const destPath = gulpConfig.get('browser.to')
73
+ const bundledFile = `${destPath}/${gulpConfig.get('browser.name')}.js`
74
+ expect.assertions(2)
75
+ bundle()
76
+ .on('finish', () => {
77
+ // A UMD bundle (browserify's `standalone` option) resolves to its CommonJS branch when
78
+ // required like this, and to a `window.<browser.name>` global when loaded via a bare
79
+ // <script> tag in a real browser - both paths hit the same exported value.
80
+ const resolved = path.resolve(bundledFile)
81
+ delete require.cache[resolved]
82
+ const loaded = require(resolved)
83
+ expect(typeof loaded.file1).toBe('function')
84
+ expect(typeof loaded.file2).toBe('function')
85
+ done()
86
+ })
87
+ .on('error', error => done(error))
88
+ })
69
89
  })
70
90
 
71
91
  describe('bundle ignore / exclude', () => {
@@ -79,7 +79,19 @@ export const describeExports = (ts, filePath) => {
79
79
  */
80
80
  export const describeFolder = folderPath => {
81
81
  const indexFile = ['index.ts', 'index.tsx', 'index.mts', 'index.cts'].map(name => path.join(folderPath, name)).find(file => fs.existsSync(file))
82
- const comment = indexFile && fs.readFileSync(indexFile, 'utf8').match(/^\s*\/\*\*([\s\S]*?)\*\//)
82
+ return indexFile ? describeFile(indexFile) : ''
83
+ }
84
+
85
+ /**
86
+ * The description of a source file for its module: its first doc comment without the tags (@module, @file, ...), when
87
+ * that comment is a header (it is followed by an import or export, so it does not document a declaration itself), or
88
+ * an empty string.
89
+ * @memberOf module:partials
90
+ * @param {string} filePath
91
+ * @returns {string}
92
+ */
93
+ export const describeFile = filePath => {
94
+ const comment = fs.readFileSync(filePath, 'utf8').match(/^\s*\/\*\*([\s\S]*?)\*\/\s*(import\b|export\s+(\*|\{[^}]*\}\s+from)|$)/)
83
95
  if (!comment) {
84
96
  return ''
85
97
  }
@@ -93,7 +105,9 @@ export const describeFolder = folderPath => {
93
105
  * entry file, so the modules of the documentation mirror the folders. The default export of each source file is
94
106
  * re-exported under the name of the file (the source files hold one function each), and anything else a file exports
95
107
  * is re-exported as it is (that is where the types.ts files go). Index files (the barrels), tests and declaration
96
- * files are left out, but the first doc comment of a folder's index file becomes the description of its module. A source directory with no folders gets a single entry called index.
108
+ * files are left out, but the first doc comment of a folder's index file becomes the description of its module.
109
+ * A file which sits beside the folders (not an index or main file) is a module of its own, described by its header
110
+ * comment. A source directory with no folders gets a single entry called index.
97
111
  * @memberOf module:partials
98
112
  * @param {Object} ts - The TypeScript compiler API.
99
113
  * @param {string} srcDir - The directory holding the TypeScript source.
@@ -107,9 +121,19 @@ export const writeDocEntries = (ts, srcDir, entryDir) => {
107
121
  .filter(entry => entry.isDirectory() && entry.name !== 'node_modules')
108
122
  .map(entry => ({ name: entry.name, path: path.join(absoluteSrc, entry.name), files: listSources(path.join(absoluteSrc, entry.name)) }))
109
123
  .filter(folder => folder.files.length)
124
+ const rootFiles = fs.readdirSync(absoluteSrc, { withFileTypes: true })
125
+ .filter(entry => entry.isFile() && isSource(entry.name) && !/^(index|main)\./.test(entry.name))
126
+ .map(entry => path.join(absoluteSrc, entry.name))
127
+ // Folders make modules, and so do the files which sit beside them (except the barrel: index and main). A source
128
+ // directory with no folders gets a single module called index which holds its files.
110
129
  const groups = folders.length
111
- ? folders
112
- : [{ name: 'index', path: absoluteSrc, files: listSources(absoluteSrc).filter(file => !/^(index|main)\./.test(path.basename(file))) }]
130
+ ? [
131
+ ...folders,
132
+ ...rootFiles
133
+ .map(file => ({ name: toIdentifier(path.basename(file).replace(/\.[cm]?tsx?$/, '')), files: [file], description: describeFile(file) }))
134
+ .filter(group => !folders.some(folder => folder.name === group.name))
135
+ ]
136
+ : [{ name: 'index', path: absoluteSrc, files: rootFiles }]
113
137
  return groups.map(group => {
114
138
  const lines = group.files
115
139
  .filter(file => !/^index\./.test(path.basename(file)))
@@ -123,7 +147,7 @@ export const writeDocEntries = (ts, srcDir, entryDir) => {
123
147
  ...(hasNamed ? [`export * from '${modulePath}'`] : [])
124
148
  ]
125
149
  })
126
- const description = group.path ? describeFolder(group.path) : ''
150
+ const description = group.description ?? (group.path ? describeFolder(group.path) : '')
127
151
  // TypeDoc takes the first comment of an entry file which has the @module tag as the description of the module
128
152
  const moduleComment = description ? `/**\n${description.split('\n').map(line => ` * ${line}`.trimEnd()).join('\n')}\n * @module\n */\n\n` : ''
129
153
  const entryPath = path.join(entryDir, `${group.name}.ts`)
@@ -2,7 +2,7 @@ import fs from 'fs'
2
2
  import path from 'node:path'
3
3
  import ts from 'typescript'
4
4
  import * as setUp from '../test-helpers/setUp.mjs'
5
- import { describeExports, describeFolder, loadTypeDoc, typeDocsFor, writeDocEntries } from './typeDocsFor.mjs'
5
+ import { describeExports, describeFile, describeFolder, loadTypeDoc, typeDocsFor, writeDocEntries } from './typeDocsFor.mjs'
6
6
 
7
7
  setUp.setDefaults('test-type-docs-for')
8
8
  const gulpConfig = setUp.gulpConfig
@@ -66,6 +66,18 @@ describe('describeFolder', () => {
66
66
  })
67
67
  })
68
68
 
69
+ describe('describeFile', () => {
70
+ test('is the header comment of a file, without its tags', () => {
71
+ write(`${gulpConfig.get('srcPath')}/header.ts`, "/**\n * Simulate what a user does.\n * @module simulate\n */\nimport a from './a'\nexport default a\n")
72
+ expect(describeFile(`${gulpConfig.get('srcPath')}/header.ts`)).toBe('Simulate what a user does.')
73
+ })
74
+
75
+ test('is empty when the first comment documents a declaration', () => {
76
+ write(`${gulpConfig.get('srcPath')}/declaration.ts`, '/**\n * Add two numbers.\n */\nconst add = (a: number, b: number) => a + b\nexport default add\n')
77
+ expect(describeFile(`${gulpConfig.get('srcPath')}/declaration.ts`)).toBe('')
78
+ })
79
+ })
80
+
69
81
  describe('writeDocEntries', () => {
70
82
  test('writes one entry file for each folder which has source', () => {
71
83
  const entryDir = `${gulpConfig.get('srcPath')}/../entries`
@@ -104,6 +116,19 @@ describe('writeDocEntries', () => {
104
116
  expect(fs.readFileSync(`${entryDir}/line.ts`, 'utf8').startsWith('export')).toBe(true)
105
117
  })
106
118
 
119
+ test('makes a module of a file which sits beside the folders, but not of the barrel files', () => {
120
+ const srcPath = gulpConfig.get('srcPath')
121
+ write(`${srcPath}/simulate.ts`, "/**\n * Simulate what a user does.\n */\nimport add from './point/add'\nexport const click = () => add(1, 2)\nexport default { click }\n")
122
+ write(`${srcPath}/simulate.test.ts`, "test('x', () => {})\n")
123
+ const entryDir = `${srcPath}/../entries`
124
+ const entries = writeDocEntries(ts, srcPath, entryDir)
125
+ expect(entries.map(entry => path.basename(entry)).sort()).toEqual(['line.ts', 'point.ts', 'simulate.ts'])
126
+ const absoluteSrc = path.resolve(srcPath).split(path.sep).join('/')
127
+ expect(fs.readFileSync(`${entryDir}/simulate.ts`, 'utf8')).toBe(
128
+ `/**\n * Simulate what a user does.\n * @module\n */\n\nexport { default as simulate } from '${absoluteSrc}/simulate'\nexport * from '${absoluteSrc}/simulate'\n`
129
+ )
130
+ })
131
+
107
132
  test('makes one entry called index when the source has no folders', () => {
108
133
  const flat = `${gulpConfig.get('srcPath')}/../flat`
109
134
  write(`${flat}/one.ts`, 'export default 1\n')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-build-tools",
3
- "version": "5.3.1",
3
+ "version": "5.4.0",
4
4
  "description": "Standardised build process with gulp.",
5
5
  "main": "gulpfile.base.js",
6
6
  "exports": {