js-build-tools 6.5.0 → 6.7.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/LICENSE +674 -674
- package/README.md +762 -692
- package/babel.config.js +28 -28
- package/dist/build.js +75 -75
- package/dist/bundle.js +19 -19
- package/dist/bundleMinify.js +7 -7
- package/dist/compileReadme.js +4 -4
- package/dist/copyFiles.js +17 -17
- package/dist/copyFonts.js +4 -4
- package/dist/default.js +5 -5
- package/dist/dist.js +4 -4
- package/dist/distMinify.js +4 -4
- package/dist/docs.js +7 -7
- package/dist/images.js +4 -4
- package/dist/jsdocDocs.js +6 -6
- package/dist/lint.js +16 -16
- package/dist/partials/addToReadme.js +9 -9
- package/dist/partials/beginWatcher.js +4 -4
- package/dist/partials/clean.js +5 -5
- package/dist/partials/copyFor.js +9 -9
- package/dist/partials/distFor.js +6 -6
- package/dist/partials/distForSrc.js +5 -5
- package/dist/partials/distSeries.js +7 -7
- package/dist/partials/imagesFor.js +93 -56
- package/dist/partials/jsdocDocsFor.js +46 -46
- package/dist/partials/lintSource.js +51 -51
- package/dist/partials/loadPeer.js +99 -99
- package/dist/partials/loadPlaywright.js +14 -14
- package/dist/partials/minifyFor.js +20 -20
- package/dist/partials/neostandardLint.js +175 -175
- package/dist/partials/readmeTemplate.js +4 -4
- package/dist/partials/removeDirectory.js +11 -11
- package/dist/partials/resolveDocsMethod.js +6 -6
- package/dist/partials/runOnChange.js +31 -31
- package/dist/partials/sassFor.js +6 -6
- package/dist/partials/serveStatic.js +9 -9
- package/dist/partials/tsFor.js +32 -32
- package/dist/partials/typeDocsFor.js +57 -51
- package/dist/partials.js +176 -176
- package/dist/sourceLint.js +18 -18
- package/dist/test-helpers/isolateGulpCache.js +24 -24
- package/dist/test-helpers/setUp.js +4 -4
- package/dist/testFull.js +5 -5
- package/dist/testQuick.js +5 -5
- package/dist/typeDocs.js +5 -5
- package/dist/typeScript.js +4 -4
- package/dist/utilities/convertMjsFilesForDocs.js +12 -12
- package/dist/utilities/dotGet.js +54 -54
- package/dist/utilities/dotNotate.js +79 -79
- package/dist/utilities/dotSet.js +45 -45
- package/dist/utilities/esmNamedExportsToCjs.js +15 -15
- package/dist/utilities/getBundleConfigs.js +9 -9
- package/dist/utilities/isObject.js +6 -6
- package/dist/utilities/strAfter.js +7 -7
- package/dist/utilities/strBefore.js +7 -7
- package/dist/watchFull.js +4 -4
- package/dist/watchTest.js +4 -4
- package/functions/build.mjs +72 -72
- package/functions/build.test.mjs +763 -759
- package/functions/bundle.mjs +44 -44
- package/functions/bundle.test.mjs +219 -219
- package/functions/bundleMinify.mjs +16 -16
- package/functions/bundleMinify.test.mjs +37 -37
- package/functions/compileReadme.mjs +9 -9
- package/functions/compileReadme.test.mjs +18 -18
- package/functions/copyFiles.mjs +32 -32
- package/functions/copyFiles.test.mjs +78 -78
- package/functions/copyFonts.mjs +9 -9
- package/functions/copyFonts.test.mjs +32 -32
- package/functions/default.mjs +12 -12
- package/functions/default.test.mjs +33 -33
- package/functions/dist.mjs +8 -8
- package/functions/dist.test.mjs +44 -44
- package/functions/distMinify.mjs +9 -9
- package/functions/distMinify.test.mjs +15 -15
- package/functions/docs.mjs +13 -13
- package/functions/docs.test.mjs +45 -45
- package/functions/gulpConfigEntries.test.mjs +23 -23
- package/functions/images.mjs +8 -8
- package/functions/images.test.mjs +142 -142
- package/functions/jsdocDocs.mjs +10 -10
- package/functions/jsdocEsmExports.test.mjs +24 -24
- package/functions/lint.mjs +9 -9
- package/functions/partials/addToReadme.mjs +25 -25
- package/functions/partials/addToReadme.test.mjs +34 -34
- package/functions/partials/beginWatcher.mjs +9 -9
- package/functions/partials/beginWatcher.test.mjs +19 -19
- package/functions/partials/clean.mjs +13 -13
- package/functions/partials/clean.test.mjs +22 -22
- package/functions/partials/copyFor.mjs +15 -15
- package/functions/partials/copyFor.test.mjs +53 -53
- package/functions/partials/distFor.mjs +22 -22
- package/functions/partials/distFor.test.mjs +195 -195
- package/functions/partials/distForSrc.mjs +11 -11
- package/functions/partials/distForSrc.test.mjs +18 -18
- package/functions/partials/distSeries.mjs +21 -21
- package/functions/partials/distSeries.test.mjs +34 -34
- package/functions/partials/imagesFor.mjs +83 -50
- package/functions/partials/imagesFor.test.mjs +287 -229
- package/functions/partials/jsdocDocsFor.mjs +163 -152
- package/functions/partials/jsdocDocsFor.test.mjs +182 -152
- package/functions/partials/lintSource.mjs +41 -41
- package/functions/partials/loadPeer.mjs +76 -76
- package/functions/partials/loadPeer.test.mjs +58 -58
- package/functions/partials/loadPlaywright.mjs +23 -23
- package/functions/partials/loadPlaywright.test.mjs +70 -70
- package/functions/partials/minifyFor.mjs +15 -15
- package/functions/partials/minifyFor.test.mjs +49 -49
- package/functions/partials/neostandardLint.mjs +142 -142
- package/functions/partials/neostandardLint.test.mjs +131 -131
- package/functions/partials/readmeTemplate.mjs +12 -12
- package/functions/partials/readmeTemplate.test.mjs +34 -34
- package/functions/partials/removeDirectory.mjs +34 -34
- package/functions/partials/removeDirectory.test.mjs +72 -72
- package/functions/partials/resolveDocsMethod.mjs +10 -10
- package/functions/partials/resolveDocsMethod.test.mjs +33 -33
- package/functions/partials/runOnChange.mjs +54 -54
- package/functions/partials/runOnChange.test.mjs +95 -95
- package/functions/partials/sassFor.mjs +25 -25
- package/functions/partials/sassFor.test.mjs +81 -81
- package/functions/partials/serveStatic.mjs +47 -47
- package/functions/partials/serveStatic.test.mjs +71 -71
- package/functions/partials/tsFor.mjs +92 -92
- package/functions/partials/tsFor.test.mjs +88 -88
- package/functions/partials/typeDocsFor.mjs +231 -227
- package/functions/partials/typeDocsFor.test.mjs +263 -252
- package/functions/partials.mjs +31 -31
- package/functions/partials.test.mjs +7 -7
- package/functions/sass.mjs +4 -4
- package/functions/sass.test.mjs +31 -31
- package/functions/sourceLint.mjs +13 -13
- package/functions/sourceLint.test.mjs +166 -166
- package/functions/test-helpers/isolateGulpCache.mjs +16 -16
- package/functions/test-helpers/neostandardLintWithRealNeostandard.mjs +40 -40
- package/functions/test-helpers/setUp.mjs +100 -100
- package/functions/test-helpers/setUp.test.mjs +19 -19
- package/functions/testFull.mjs +17 -17
- package/functions/testFull.test.mjs +17 -17
- package/functions/testHelpers.mjs +10 -10
- package/functions/testHelpers.test.mjs +7 -7
- package/functions/testQuick.mjs +17 -17
- package/functions/testQuick.test.mjs +17 -17
- package/functions/typeDocs.mjs +9 -9
- package/functions/typeDocs.test.mjs +11 -11
- package/functions/typeScript.mjs +8 -8
- package/functions/typeScript.test.mjs +11 -11
- package/functions/utilities/convertMjsFilesForDocs.mjs +34 -34
- package/functions/utilities/convertMjsFilesForDocs.test.mjs +58 -58
- package/functions/utilities/dotGet.mjs +49 -49
- package/functions/utilities/dotNotate.mjs +73 -73
- package/functions/utilities/dotNotate.test.mjs +41 -41
- package/functions/utilities/dotSet.mjs +40 -40
- package/functions/utilities/esmNamedExportsToCjs.mjs +30 -30
- package/functions/utilities/esmNamedExportsToCjs.test.mjs +47 -47
- package/functions/utilities/getBundleConfigs.mjs +30 -30
- package/functions/utilities/isObject.mjs +8 -8
- package/functions/utilities/strAfter.mjs +12 -12
- package/functions/utilities/strBefore.mjs +12 -12
- package/functions/watchFull.mjs +9 -9
- package/functions/watchFull.test.mjs +41 -41
- package/functions/watchTest.mjs +10 -10
- package/functions/watchTest.test.mjs +49 -49
- package/gulp.config.js +4 -4
- package/gulp.config.mjs +344 -327
- package/gulpfile.base.js +51 -51
- package/gulpfile.base.mjs +29 -29
- package/jsdoc.base.js +16 -16
- package/jsdoc.esmExports.js +17 -17
- package/package.json +146 -145
package/functions/bundle.mjs
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import browserify from 'browserify'
|
|
2
|
-
import { dest } from 'gulp'
|
|
3
|
-
import mergeStream from 'merge-stream'
|
|
4
|
-
import * as gulpConfig from '../gulp.config.mjs'
|
|
5
|
-
import source from 'vinyl-source-stream'
|
|
6
|
-
import { getBundleConfigs } from './utilities/getBundleConfigs.mjs'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Starting at each bundle's own entry point (dist.main for the primary bundle, or its own
|
|
10
|
-
* override from browser.bundles), bundle all the reachable files into a single file per bundle and
|
|
11
|
-
* store them in the specified output directory. A project with no browser.bundles entries gets
|
|
12
|
-
* exactly the one bundle it always did; each additional entry produces its own separate bundle
|
|
13
|
-
* (for example, a render-only variant built from a different entry point than the full bundle).
|
|
14
|
-
*
|
|
15
|
-
* A bundle's name does double duty: it names the output file (name.js) and, via browserify's
|
|
16
|
-
* `standalone` option, is also the name its exports are exposed under - as a global when loaded
|
|
17
|
-
* with a bare `<script>` tag, or as the module when required via CommonJS/AMD. Without this, a
|
|
18
|
-
* bundle built from a real entry point (one that actually exports something) builds successfully
|
|
19
|
-
* but exposes nothing a consumer can reach.
|
|
20
|
-
*
|
|
21
|
-
* Two escape hatches from browser.config.json keep an unwanted dependency (or a dependency's dependency) out of the
|
|
22
|
-
* bundle: 'ignore' replaces it with an empty object (safe when your code never actually calls into it - for example
|
|
23
|
-
* a library's default option value you always override with your own) and 'exclude' leaves it out entirely (your
|
|
24
|
-
* code must not require it, or provide it another way, such as a separate script tag).
|
|
25
|
-
* @memberOf module:js-build-tools
|
|
26
|
-
* @returns {stream.Stream}
|
|
27
|
-
*/
|
|
28
|
-
export const bundle = () => mergeStream(
|
|
29
|
-
getBundleConfigs().map(({ name, main, ignore, exclude }) => {
|
|
30
|
-
const bundler = browserify(main, { standalone: name })
|
|
31
|
-
if (ignore.length) {
|
|
32
|
-
bundler.ignore(ignore)
|
|
33
|
-
}
|
|
34
|
-
if (exclude.length) {
|
|
35
|
-
bundler.exclude(exclude)
|
|
36
|
-
}
|
|
37
|
-
return bundler
|
|
38
|
-
.bundle()
|
|
39
|
-
.pipe(source(`${name}.js`))
|
|
40
|
-
})
|
|
41
|
-
// One shared dest() for every bundle, not one per bundle - vinyl-fs's dest() races against
|
|
42
|
-
// itself (a real "write after end" failure) when two separate dest() streams target the same
|
|
43
|
-
// directory concurrently, which every additional bundle's own dest() call otherwise would.
|
|
44
|
-
).pipe(dest(gulpConfig.get('browser.to')))
|
|
1
|
+
import browserify from 'browserify'
|
|
2
|
+
import { dest } from 'gulp'
|
|
3
|
+
import mergeStream from 'merge-stream'
|
|
4
|
+
import * as gulpConfig from '../gulp.config.mjs'
|
|
5
|
+
import source from 'vinyl-source-stream'
|
|
6
|
+
import { getBundleConfigs } from './utilities/getBundleConfigs.mjs'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Starting at each bundle's own entry point (dist.main for the primary bundle, or its own
|
|
10
|
+
* override from browser.bundles), bundle all the reachable files into a single file per bundle and
|
|
11
|
+
* store them in the specified output directory. A project with no browser.bundles entries gets
|
|
12
|
+
* exactly the one bundle it always did; each additional entry produces its own separate bundle
|
|
13
|
+
* (for example, a render-only variant built from a different entry point than the full bundle).
|
|
14
|
+
*
|
|
15
|
+
* A bundle's name does double duty: it names the output file (name.js) and, via browserify's
|
|
16
|
+
* `standalone` option, is also the name its exports are exposed under - as a global when loaded
|
|
17
|
+
* with a bare `<script>` tag, or as the module when required via CommonJS/AMD. Without this, a
|
|
18
|
+
* bundle built from a real entry point (one that actually exports something) builds successfully
|
|
19
|
+
* but exposes nothing a consumer can reach.
|
|
20
|
+
*
|
|
21
|
+
* Two escape hatches from browser.config.json keep an unwanted dependency (or a dependency's dependency) out of the
|
|
22
|
+
* bundle: 'ignore' replaces it with an empty object (safe when your code never actually calls into it - for example
|
|
23
|
+
* a library's default option value you always override with your own) and 'exclude' leaves it out entirely (your
|
|
24
|
+
* code must not require it, or provide it another way, such as a separate script tag).
|
|
25
|
+
* @memberOf module:js-build-tools
|
|
26
|
+
* @returns {stream.Stream}
|
|
27
|
+
*/
|
|
28
|
+
export const bundle = () => mergeStream(
|
|
29
|
+
getBundleConfigs().map(({ name, main, ignore, exclude }) => {
|
|
30
|
+
const bundler = browserify(main, { standalone: name })
|
|
31
|
+
if (ignore.length) {
|
|
32
|
+
bundler.ignore(ignore)
|
|
33
|
+
}
|
|
34
|
+
if (exclude.length) {
|
|
35
|
+
bundler.exclude(exclude)
|
|
36
|
+
}
|
|
37
|
+
return bundler
|
|
38
|
+
.bundle()
|
|
39
|
+
.pipe(source(`${name}.js`))
|
|
40
|
+
})
|
|
41
|
+
// One shared dest() for every bundle, not one per bundle - vinyl-fs's dest() races against
|
|
42
|
+
// itself (a real "write after end" failure) when two separate dest() streams target the same
|
|
43
|
+
// directory concurrently, which every additional bundle's own dest() call otherwise would.
|
|
44
|
+
).pipe(dest(gulpConfig.get('browser.to')))
|
|
@@ -1,219 +1,219 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import * as setUp from './test-helpers/setUp.mjs'
|
|
4
|
-
import { bundle } from './bundle.mjs'
|
|
5
|
-
import { countMatches } from 'test-filesystem'
|
|
6
|
-
|
|
7
|
-
setUp.setDefaults('test-bundle')
|
|
8
|
-
const gulpConfig = setUp.gulpConfig
|
|
9
|
-
|
|
10
|
-
const file1Contents = 'function file1 () {\n' +
|
|
11
|
-
' console.log(\'Hello from file1\')\n' +
|
|
12
|
-
'}\n' +
|
|
13
|
-
'\n' +
|
|
14
|
-
'module.exports = file1\n'
|
|
15
|
-
const file2Contents = 'function file2 () {\n' +
|
|
16
|
-
' console.log(\'Good-bye from file2\')\n' +
|
|
17
|
-
'}\n' +
|
|
18
|
-
'\n' +
|
|
19
|
-
'module.exports = file2\n'
|
|
20
|
-
const mainFileContents = 'const file1 = require(\'./sources/file1\')\n' +
|
|
21
|
-
'const file2 = require(\'./sources/file2\')\n' +
|
|
22
|
-
'\n' +
|
|
23
|
-
'module.exports = {\n' +
|
|
24
|
-
' file1,\n' +
|
|
25
|
-
' file2,\n' +
|
|
26
|
-
'}\n'
|
|
27
|
-
|
|
28
|
-
beforeEach(
|
|
29
|
-
() => setUp.beforeEach()
|
|
30
|
-
.then(
|
|
31
|
-
async () => {
|
|
32
|
-
const distPath = gulpConfig.get('dist.to')
|
|
33
|
-
const mainFile = gulpConfig.get('dist.main')
|
|
34
|
-
const sourcesPath = `${distPath}/sources`
|
|
35
|
-
await fs.mkdirSync(distPath)
|
|
36
|
-
await fs.mkdirSync(sourcesPath)
|
|
37
|
-
await fs.writeFileSync(`${sourcesPath}/file1.js`, file1Contents)
|
|
38
|
-
await fs.writeFileSync(`${sourcesPath}/file2.js`, file2Contents)
|
|
39
|
-
return fs.writeFileSync(`${mainFile}.js`, mainFileContents)
|
|
40
|
-
}
|
|
41
|
-
)
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
afterEach(setUp.afterEach)
|
|
45
|
-
|
|
46
|
-
describe('bundle', () => {
|
|
47
|
-
test('copies all the dist contents into a single file for browsers', done => {
|
|
48
|
-
const distPath = gulpConfig.get('dist.to')
|
|
49
|
-
const destFile = `${gulpConfig.get('browser.name')}.js`
|
|
50
|
-
const destPath = gulpConfig.get('browser.to')
|
|
51
|
-
const bundledFile = `${destPath}/${destFile}`
|
|
52
|
-
const sourcesPath = `${distPath}/sources`
|
|
53
|
-
const file1Path = `${sourcesPath}/file1.js`
|
|
54
|
-
const file2Path = `${sourcesPath}/file2.js`
|
|
55
|
-
const oldFile1Contents = fs.readFileSync(file1Path).toString()
|
|
56
|
-
const oldFile2Contents = fs.readFileSync(file2Path).toString()
|
|
57
|
-
expect.assertions(2)
|
|
58
|
-
bundle()
|
|
59
|
-
.on('finish', () => {
|
|
60
|
-
const bundledContents = fs.readFileSync(bundledFile).toString()
|
|
61
|
-
expect(countMatches(bundledContents, oldFile1Contents)).toEqual(1)
|
|
62
|
-
expect(countMatches(bundledContents, oldFile2Contents)).toEqual(1)
|
|
63
|
-
done()
|
|
64
|
-
})
|
|
65
|
-
.on('error', error => {
|
|
66
|
-
console.error('Encountered error', error)
|
|
67
|
-
done()
|
|
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
|
-
})
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
describe('bundle with additional bundles', () => {
|
|
92
|
-
const renderOnlyFileContents = 'const file1 = require(\'./sources/file1\')\n' +
|
|
93
|
-
'\n' +
|
|
94
|
-
'module.exports = {\n' +
|
|
95
|
-
' file1,\n' +
|
|
96
|
-
'}\n'
|
|
97
|
-
|
|
98
|
-
afterEach(() => gulpConfig.set('browser.bundles', []))
|
|
99
|
-
|
|
100
|
-
test('produces one file per additional bundle, each from its own entry point', done => {
|
|
101
|
-
const distPath = gulpConfig.get('dist.to')
|
|
102
|
-
const renderOnlyMain = `${distPath}/renderOnly`
|
|
103
|
-
fs.writeFileSync(`${renderOnlyMain}.js`, renderOnlyFileContents)
|
|
104
|
-
gulpConfig.set('browser.bundles', [{ name: 'renderOnly', main: renderOnlyMain }])
|
|
105
|
-
|
|
106
|
-
const destPath = gulpConfig.get('browser.to')
|
|
107
|
-
const primaryFile = `${destPath}/${gulpConfig.get('browser.name')}.js`
|
|
108
|
-
const renderOnlyFile = `${destPath}/renderOnly.js`
|
|
109
|
-
expect.assertions(4)
|
|
110
|
-
bundle()
|
|
111
|
-
.on('finish', () => {
|
|
112
|
-
// The primary bundle is unaffected - still built from dist.main, still has both files.
|
|
113
|
-
const primaryContents = fs.readFileSync(primaryFile).toString()
|
|
114
|
-
expect(primaryContents).toContain('Hello from file1')
|
|
115
|
-
expect(primaryContents).toContain('Good-bye from file2')
|
|
116
|
-
|
|
117
|
-
// The additional bundle came from its own entry point - file1 only, never file2.
|
|
118
|
-
const renderOnlyContents = fs.readFileSync(renderOnlyFile).toString()
|
|
119
|
-
expect(renderOnlyContents).toContain('Hello from file1')
|
|
120
|
-
expect(renderOnlyContents).not.toContain('Good-bye from file2')
|
|
121
|
-
done()
|
|
122
|
-
})
|
|
123
|
-
.on('error', error => done(error))
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
test('an additional bundle exposes its own name as its standalone export', done => {
|
|
127
|
-
const distPath = gulpConfig.get('dist.to')
|
|
128
|
-
const renderOnlyMain = `${distPath}/renderOnly`
|
|
129
|
-
fs.writeFileSync(`${renderOnlyMain}.js`, renderOnlyFileContents)
|
|
130
|
-
gulpConfig.set('browser.bundles', [{ name: 'renderOnly', main: renderOnlyMain }])
|
|
131
|
-
|
|
132
|
-
const destPath = gulpConfig.get('browser.to')
|
|
133
|
-
const renderOnlyFile = `${destPath}/renderOnly.js`
|
|
134
|
-
expect.assertions(2)
|
|
135
|
-
bundle()
|
|
136
|
-
.on('finish', () => {
|
|
137
|
-
const resolved = path.resolve(renderOnlyFile)
|
|
138
|
-
delete require.cache[resolved]
|
|
139
|
-
const loaded = require(resolved)
|
|
140
|
-
expect(typeof loaded.file1).toBe('function')
|
|
141
|
-
expect(loaded.file2).toBeUndefined()
|
|
142
|
-
done()
|
|
143
|
-
})
|
|
144
|
-
.on('error', error => done(error))
|
|
145
|
-
})
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
describe('bundle ignore / exclude', () => {
|
|
149
|
-
const optionalDepMarker = 'MARKER_FROM_THE_OPTIONAL_DEPENDENCY_CODE'
|
|
150
|
-
|
|
151
|
-
beforeEach(() => {
|
|
152
|
-
const tempDir = gulpConfig.get('rootPath')
|
|
153
|
-
const depDir = `${tempDir}node_modules/optional-dep`
|
|
154
|
-
fs.mkdirSync(depDir, { recursive: true })
|
|
155
|
-
fs.writeFileSync(`${depDir}/package.json`, JSON.stringify({ name: 'optional-dep', main: 'index.js' }))
|
|
156
|
-
fs.writeFileSync(`${depDir}/index.js`, `module.exports = '${optionalDepMarker}'\n`)
|
|
157
|
-
const distPath = gulpConfig.get('dist.to')
|
|
158
|
-
const mainFile = gulpConfig.get('dist.main')
|
|
159
|
-
fs.mkdirSync(distPath, { recursive: true })
|
|
160
|
-
// Only reached as a fallback for an option the caller always overrides, the way css-select falls back to its
|
|
161
|
-
// own default adapter (domutils) unless one is given - exactly the case ignore / exclude exist for.
|
|
162
|
-
fs.writeFileSync(`${mainFile}.js`, 'const optionalDep = require(\'optional-dep\')\n\nmodule.exports = (given) => given || optionalDep\n')
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
test('without ignore or exclude, the dependency is bundled', done => {
|
|
166
|
-
const destPath = gulpConfig.get('browser.to')
|
|
167
|
-
const bundledFile = `${destPath}/${gulpConfig.get('browser.name')}.js`
|
|
168
|
-
expect.assertions(1)
|
|
169
|
-
bundle()
|
|
170
|
-
.on('finish', () => {
|
|
171
|
-
expect(fs.readFileSync(bundledFile).toString()).toContain(optionalDepMarker)
|
|
172
|
-
done()
|
|
173
|
-
})
|
|
174
|
-
.on('error', error => done(error))
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
test('ignore replaces the dependency with an empty object, but the bundle still finishes', done => {
|
|
178
|
-
gulpConfig.set('browser.ignore', ['optional-dep'])
|
|
179
|
-
const destPath = gulpConfig.get('browser.to')
|
|
180
|
-
const bundledFile = `${destPath}/${gulpConfig.get('browser.name')}.js`
|
|
181
|
-
expect.assertions(2)
|
|
182
|
-
bundle()
|
|
183
|
-
.on('finish', () => {
|
|
184
|
-
const bundledContents = fs.readFileSync(bundledFile).toString()
|
|
185
|
-
// The ignored module is still in the bundle (as an empty stub, so requiring it by name still resolves) -
|
|
186
|
-
// it just no longer has the real code that would have pulled in the marker
|
|
187
|
-
expect(bundledContents).toContain('optional-dep')
|
|
188
|
-
expect(bundledContents).not.toContain(optionalDepMarker)
|
|
189
|
-
done()
|
|
190
|
-
})
|
|
191
|
-
.on('error', error => done(error))
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
test('exclude leaves the dependency out of the bundle entirely', done => {
|
|
195
|
-
gulpConfig.set('browser.exclude', ['optional-dep'])
|
|
196
|
-
const destPath = gulpConfig.get('browser.to')
|
|
197
|
-
const bundledFile = `${destPath}/${gulpConfig.get('browser.name')}.js`
|
|
198
|
-
expect.assertions(1)
|
|
199
|
-
bundle()
|
|
200
|
-
.on('finish', () => {
|
|
201
|
-
expect(fs.readFileSync(bundledFile).toString()).not.toContain(optionalDepMarker)
|
|
202
|
-
done()
|
|
203
|
-
})
|
|
204
|
-
.on('error', error => done(error))
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
test('a single string is accepted the same as a one item array', done => {
|
|
208
|
-
gulpConfig.set('browser.ignore', 'optional-dep')
|
|
209
|
-
const destPath = gulpConfig.get('browser.to')
|
|
210
|
-
const bundledFile = `${destPath}/${gulpConfig.get('browser.name')}.js`
|
|
211
|
-
expect.assertions(1)
|
|
212
|
-
bundle()
|
|
213
|
-
.on('finish', () => {
|
|
214
|
-
expect(fs.readFileSync(bundledFile).toString()).not.toContain(optionalDepMarker)
|
|
215
|
-
done()
|
|
216
|
-
})
|
|
217
|
-
.on('error', error => done(error))
|
|
218
|
-
})
|
|
219
|
-
})
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import * as setUp from './test-helpers/setUp.mjs'
|
|
4
|
+
import { bundle } from './bundle.mjs'
|
|
5
|
+
import { countMatches } from 'test-filesystem'
|
|
6
|
+
|
|
7
|
+
setUp.setDefaults('test-bundle')
|
|
8
|
+
const gulpConfig = setUp.gulpConfig
|
|
9
|
+
|
|
10
|
+
const file1Contents = 'function file1 () {\n' +
|
|
11
|
+
' console.log(\'Hello from file1\')\n' +
|
|
12
|
+
'}\n' +
|
|
13
|
+
'\n' +
|
|
14
|
+
'module.exports = file1\n'
|
|
15
|
+
const file2Contents = 'function file2 () {\n' +
|
|
16
|
+
' console.log(\'Good-bye from file2\')\n' +
|
|
17
|
+
'}\n' +
|
|
18
|
+
'\n' +
|
|
19
|
+
'module.exports = file2\n'
|
|
20
|
+
const mainFileContents = 'const file1 = require(\'./sources/file1\')\n' +
|
|
21
|
+
'const file2 = require(\'./sources/file2\')\n' +
|
|
22
|
+
'\n' +
|
|
23
|
+
'module.exports = {\n' +
|
|
24
|
+
' file1,\n' +
|
|
25
|
+
' file2,\n' +
|
|
26
|
+
'}\n'
|
|
27
|
+
|
|
28
|
+
beforeEach(
|
|
29
|
+
() => setUp.beforeEach()
|
|
30
|
+
.then(
|
|
31
|
+
async () => {
|
|
32
|
+
const distPath = gulpConfig.get('dist.to')
|
|
33
|
+
const mainFile = gulpConfig.get('dist.main')
|
|
34
|
+
const sourcesPath = `${distPath}/sources`
|
|
35
|
+
await fs.mkdirSync(distPath)
|
|
36
|
+
await fs.mkdirSync(sourcesPath)
|
|
37
|
+
await fs.writeFileSync(`${sourcesPath}/file1.js`, file1Contents)
|
|
38
|
+
await fs.writeFileSync(`${sourcesPath}/file2.js`, file2Contents)
|
|
39
|
+
return fs.writeFileSync(`${mainFile}.js`, mainFileContents)
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
afterEach(setUp.afterEach)
|
|
45
|
+
|
|
46
|
+
describe('bundle', () => {
|
|
47
|
+
test('copies all the dist contents into a single file for browsers', done => {
|
|
48
|
+
const distPath = gulpConfig.get('dist.to')
|
|
49
|
+
const destFile = `${gulpConfig.get('browser.name')}.js`
|
|
50
|
+
const destPath = gulpConfig.get('browser.to')
|
|
51
|
+
const bundledFile = `${destPath}/${destFile}`
|
|
52
|
+
const sourcesPath = `${distPath}/sources`
|
|
53
|
+
const file1Path = `${sourcesPath}/file1.js`
|
|
54
|
+
const file2Path = `${sourcesPath}/file2.js`
|
|
55
|
+
const oldFile1Contents = fs.readFileSync(file1Path).toString()
|
|
56
|
+
const oldFile2Contents = fs.readFileSync(file2Path).toString()
|
|
57
|
+
expect.assertions(2)
|
|
58
|
+
bundle()
|
|
59
|
+
.on('finish', () => {
|
|
60
|
+
const bundledContents = fs.readFileSync(bundledFile).toString()
|
|
61
|
+
expect(countMatches(bundledContents, oldFile1Contents)).toEqual(1)
|
|
62
|
+
expect(countMatches(bundledContents, oldFile2Contents)).toEqual(1)
|
|
63
|
+
done()
|
|
64
|
+
})
|
|
65
|
+
.on('error', error => {
|
|
66
|
+
console.error('Encountered error', error)
|
|
67
|
+
done()
|
|
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
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
describe('bundle with additional bundles', () => {
|
|
92
|
+
const renderOnlyFileContents = 'const file1 = require(\'./sources/file1\')\n' +
|
|
93
|
+
'\n' +
|
|
94
|
+
'module.exports = {\n' +
|
|
95
|
+
' file1,\n' +
|
|
96
|
+
'}\n'
|
|
97
|
+
|
|
98
|
+
afterEach(() => gulpConfig.set('browser.bundles', []))
|
|
99
|
+
|
|
100
|
+
test('produces one file per additional bundle, each from its own entry point', done => {
|
|
101
|
+
const distPath = gulpConfig.get('dist.to')
|
|
102
|
+
const renderOnlyMain = `${distPath}/renderOnly`
|
|
103
|
+
fs.writeFileSync(`${renderOnlyMain}.js`, renderOnlyFileContents)
|
|
104
|
+
gulpConfig.set('browser.bundles', [{ name: 'renderOnly', main: renderOnlyMain }])
|
|
105
|
+
|
|
106
|
+
const destPath = gulpConfig.get('browser.to')
|
|
107
|
+
const primaryFile = `${destPath}/${gulpConfig.get('browser.name')}.js`
|
|
108
|
+
const renderOnlyFile = `${destPath}/renderOnly.js`
|
|
109
|
+
expect.assertions(4)
|
|
110
|
+
bundle()
|
|
111
|
+
.on('finish', () => {
|
|
112
|
+
// The primary bundle is unaffected - still built from dist.main, still has both files.
|
|
113
|
+
const primaryContents = fs.readFileSync(primaryFile).toString()
|
|
114
|
+
expect(primaryContents).toContain('Hello from file1')
|
|
115
|
+
expect(primaryContents).toContain('Good-bye from file2')
|
|
116
|
+
|
|
117
|
+
// The additional bundle came from its own entry point - file1 only, never file2.
|
|
118
|
+
const renderOnlyContents = fs.readFileSync(renderOnlyFile).toString()
|
|
119
|
+
expect(renderOnlyContents).toContain('Hello from file1')
|
|
120
|
+
expect(renderOnlyContents).not.toContain('Good-bye from file2')
|
|
121
|
+
done()
|
|
122
|
+
})
|
|
123
|
+
.on('error', error => done(error))
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('an additional bundle exposes its own name as its standalone export', done => {
|
|
127
|
+
const distPath = gulpConfig.get('dist.to')
|
|
128
|
+
const renderOnlyMain = `${distPath}/renderOnly`
|
|
129
|
+
fs.writeFileSync(`${renderOnlyMain}.js`, renderOnlyFileContents)
|
|
130
|
+
gulpConfig.set('browser.bundles', [{ name: 'renderOnly', main: renderOnlyMain }])
|
|
131
|
+
|
|
132
|
+
const destPath = gulpConfig.get('browser.to')
|
|
133
|
+
const renderOnlyFile = `${destPath}/renderOnly.js`
|
|
134
|
+
expect.assertions(2)
|
|
135
|
+
bundle()
|
|
136
|
+
.on('finish', () => {
|
|
137
|
+
const resolved = path.resolve(renderOnlyFile)
|
|
138
|
+
delete require.cache[resolved]
|
|
139
|
+
const loaded = require(resolved)
|
|
140
|
+
expect(typeof loaded.file1).toBe('function')
|
|
141
|
+
expect(loaded.file2).toBeUndefined()
|
|
142
|
+
done()
|
|
143
|
+
})
|
|
144
|
+
.on('error', error => done(error))
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
describe('bundle ignore / exclude', () => {
|
|
149
|
+
const optionalDepMarker = 'MARKER_FROM_THE_OPTIONAL_DEPENDENCY_CODE'
|
|
150
|
+
|
|
151
|
+
beforeEach(() => {
|
|
152
|
+
const tempDir = gulpConfig.get('rootPath')
|
|
153
|
+
const depDir = `${tempDir}node_modules/optional-dep`
|
|
154
|
+
fs.mkdirSync(depDir, { recursive: true })
|
|
155
|
+
fs.writeFileSync(`${depDir}/package.json`, JSON.stringify({ name: 'optional-dep', main: 'index.js' }))
|
|
156
|
+
fs.writeFileSync(`${depDir}/index.js`, `module.exports = '${optionalDepMarker}'\n`)
|
|
157
|
+
const distPath = gulpConfig.get('dist.to')
|
|
158
|
+
const mainFile = gulpConfig.get('dist.main')
|
|
159
|
+
fs.mkdirSync(distPath, { recursive: true })
|
|
160
|
+
// Only reached as a fallback for an option the caller always overrides, the way css-select falls back to its
|
|
161
|
+
// own default adapter (domutils) unless one is given - exactly the case ignore / exclude exist for.
|
|
162
|
+
fs.writeFileSync(`${mainFile}.js`, 'const optionalDep = require(\'optional-dep\')\n\nmodule.exports = (given) => given || optionalDep\n')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('without ignore or exclude, the dependency is bundled', done => {
|
|
166
|
+
const destPath = gulpConfig.get('browser.to')
|
|
167
|
+
const bundledFile = `${destPath}/${gulpConfig.get('browser.name')}.js`
|
|
168
|
+
expect.assertions(1)
|
|
169
|
+
bundle()
|
|
170
|
+
.on('finish', () => {
|
|
171
|
+
expect(fs.readFileSync(bundledFile).toString()).toContain(optionalDepMarker)
|
|
172
|
+
done()
|
|
173
|
+
})
|
|
174
|
+
.on('error', error => done(error))
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test('ignore replaces the dependency with an empty object, but the bundle still finishes', done => {
|
|
178
|
+
gulpConfig.set('browser.ignore', ['optional-dep'])
|
|
179
|
+
const destPath = gulpConfig.get('browser.to')
|
|
180
|
+
const bundledFile = `${destPath}/${gulpConfig.get('browser.name')}.js`
|
|
181
|
+
expect.assertions(2)
|
|
182
|
+
bundle()
|
|
183
|
+
.on('finish', () => {
|
|
184
|
+
const bundledContents = fs.readFileSync(bundledFile).toString()
|
|
185
|
+
// The ignored module is still in the bundle (as an empty stub, so requiring it by name still resolves) -
|
|
186
|
+
// it just no longer has the real code that would have pulled in the marker
|
|
187
|
+
expect(bundledContents).toContain('optional-dep')
|
|
188
|
+
expect(bundledContents).not.toContain(optionalDepMarker)
|
|
189
|
+
done()
|
|
190
|
+
})
|
|
191
|
+
.on('error', error => done(error))
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
test('exclude leaves the dependency out of the bundle entirely', done => {
|
|
195
|
+
gulpConfig.set('browser.exclude', ['optional-dep'])
|
|
196
|
+
const destPath = gulpConfig.get('browser.to')
|
|
197
|
+
const bundledFile = `${destPath}/${gulpConfig.get('browser.name')}.js`
|
|
198
|
+
expect.assertions(1)
|
|
199
|
+
bundle()
|
|
200
|
+
.on('finish', () => {
|
|
201
|
+
expect(fs.readFileSync(bundledFile).toString()).not.toContain(optionalDepMarker)
|
|
202
|
+
done()
|
|
203
|
+
})
|
|
204
|
+
.on('error', error => done(error))
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test('a single string is accepted the same as a one item array', done => {
|
|
208
|
+
gulpConfig.set('browser.ignore', 'optional-dep')
|
|
209
|
+
const destPath = gulpConfig.get('browser.to')
|
|
210
|
+
const bundledFile = `${destPath}/${gulpConfig.get('browser.name')}.js`
|
|
211
|
+
expect.assertions(1)
|
|
212
|
+
bundle()
|
|
213
|
+
.on('finish', () => {
|
|
214
|
+
expect(fs.readFileSync(bundledFile).toString()).not.toContain(optionalDepMarker)
|
|
215
|
+
done()
|
|
216
|
+
})
|
|
217
|
+
.on('error', error => done(error))
|
|
218
|
+
})
|
|
219
|
+
})
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import * as gulpConfig from '../gulp.config.mjs'
|
|
2
|
-
import { minifyFor } from './partials/minifyFor.mjs'
|
|
3
|
-
import { getBundleConfigs } from './utilities/getBundleConfigs.mjs'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Creates the minified file for every bundle (the primary bundle, plus any additional ones from
|
|
7
|
-
* browser.bundles). The minify step itself never varies per bundle, so this is one shared
|
|
8
|
-
* src()/dest() pipeline (inside minifyFor) fed every bundle's filename at once, rather than one
|
|
9
|
-
* pipeline per bundle - gulp's src() accepts an array of paths natively.
|
|
10
|
-
* @memberOf module:js-build-tools
|
|
11
|
-
* @returns {*}
|
|
12
|
-
*/
|
|
13
|
-
export const bundleMinify = () => minifyFor(
|
|
14
|
-
getBundleConfigs().map(({ name }) => `${gulpConfig.get('browser.to')}/${name}.js`),
|
|
15
|
-
gulpConfig.get('browser.to')
|
|
16
|
-
)
|
|
1
|
+
import * as gulpConfig from '../gulp.config.mjs'
|
|
2
|
+
import { minifyFor } from './partials/minifyFor.mjs'
|
|
3
|
+
import { getBundleConfigs } from './utilities/getBundleConfigs.mjs'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates the minified file for every bundle (the primary bundle, plus any additional ones from
|
|
7
|
+
* browser.bundles). The minify step itself never varies per bundle, so this is one shared
|
|
8
|
+
* src()/dest() pipeline (inside minifyFor) fed every bundle's filename at once, rather than one
|
|
9
|
+
* pipeline per bundle - gulp's src() accepts an array of paths natively.
|
|
10
|
+
* @memberOf module:js-build-tools
|
|
11
|
+
* @returns {*}
|
|
12
|
+
*/
|
|
13
|
+
export const bundleMinify = () => minifyFor(
|
|
14
|
+
getBundleConfigs().map(({ name }) => `${gulpConfig.get('browser.to')}/${name}.js`),
|
|
15
|
+
gulpConfig.get('browser.to')
|
|
16
|
+
)
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import { testHelpers } from './testHelpers.mjs'
|
|
2
|
-
import { bundleMinify } from './bundleMinify.mjs'
|
|
3
|
-
import { minifyFor } from './partials/minifyFor.mjs'
|
|
4
|
-
|
|
5
|
-
const gulpConfig = testHelpers.gulpConfig
|
|
6
|
-
|
|
7
|
-
jest.mock('./partials/minifyFor.mjs', () => ({ minifyFor: jest.fn(() => Promise.resolve(true)) }))
|
|
8
|
-
|
|
9
|
-
describe('bundleMinify', () => {
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
gulpConfig.set('browser.bundles', [])
|
|
12
|
-
minifyFor.mockClear()
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
test('calls minifyFor with the primary bundle', () => {
|
|
16
|
-
expect.assertions(1)
|
|
17
|
-
bundleMinify()
|
|
18
|
-
expect(minifyFor).toHaveBeenCalledWith(
|
|
19
|
-
[`${gulpConfig.get('browser.to')}/${gulpConfig.get('browser.name')}.js`],
|
|
20
|
-
gulpConfig.get('browser.to')
|
|
21
|
-
)
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
test('calls minifyFor once, with every additional bundle included in the same array', () => {
|
|
25
|
-
gulpConfig.set('browser.bundles', [{ name: 'renderOnly' }])
|
|
26
|
-
expect.assertions(2)
|
|
27
|
-
bundleMinify()
|
|
28
|
-
expect(minifyFor).toHaveBeenCalledTimes(1)
|
|
29
|
-
expect(minifyFor).toHaveBeenCalledWith(
|
|
30
|
-
[
|
|
31
|
-
`${gulpConfig.get('browser.to')}/${gulpConfig.get('browser.name')}.js`,
|
|
32
|
-
`${gulpConfig.get('browser.to')}/renderOnly.js`
|
|
33
|
-
],
|
|
34
|
-
gulpConfig.get('browser.to')
|
|
35
|
-
)
|
|
36
|
-
})
|
|
37
|
-
})
|
|
1
|
+
import { testHelpers } from './testHelpers.mjs'
|
|
2
|
+
import { bundleMinify } from './bundleMinify.mjs'
|
|
3
|
+
import { minifyFor } from './partials/minifyFor.mjs'
|
|
4
|
+
|
|
5
|
+
const gulpConfig = testHelpers.gulpConfig
|
|
6
|
+
|
|
7
|
+
jest.mock('./partials/minifyFor.mjs', () => ({ minifyFor: jest.fn(() => Promise.resolve(true)) }))
|
|
8
|
+
|
|
9
|
+
describe('bundleMinify', () => {
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
gulpConfig.set('browser.bundles', [])
|
|
12
|
+
minifyFor.mockClear()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('calls minifyFor with the primary bundle', () => {
|
|
16
|
+
expect.assertions(1)
|
|
17
|
+
bundleMinify()
|
|
18
|
+
expect(minifyFor).toHaveBeenCalledWith(
|
|
19
|
+
[`${gulpConfig.get('browser.to')}/${gulpConfig.get('browser.name')}.js`],
|
|
20
|
+
gulpConfig.get('browser.to')
|
|
21
|
+
)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('calls minifyFor once, with every additional bundle included in the same array', () => {
|
|
25
|
+
gulpConfig.set('browser.bundles', [{ name: 'renderOnly' }])
|
|
26
|
+
expect.assertions(2)
|
|
27
|
+
bundleMinify()
|
|
28
|
+
expect(minifyFor).toHaveBeenCalledTimes(1)
|
|
29
|
+
expect(minifyFor).toHaveBeenCalledWith(
|
|
30
|
+
[
|
|
31
|
+
`${gulpConfig.get('browser.to')}/${gulpConfig.get('browser.name')}.js`,
|
|
32
|
+
`${gulpConfig.get('browser.to')}/renderOnly.js`
|
|
33
|
+
],
|
|
34
|
+
gulpConfig.get('browser.to')
|
|
35
|
+
)
|
|
36
|
+
})
|
|
37
|
+
})
|