diff --git a/.gitignore b/.gitignore index a0056ed6b..c34ea5a34 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,9 @@ logs/ .yarnclean # Directories -coverage/ +assets/ build/ +coverage/ cjs/ dist/ dts/ diff --git a/packages/packemon/package.json b/packages/packemon/package.json index 088cb9cda..52176f852 100644 --- a/packages/packemon/package.json +++ b/packages/packemon/package.json @@ -80,6 +80,7 @@ "ink": "^3.2.0", "ink-progress-bar": "^3.0.0", "ink-spinner": "^4.0.3", + "magic-string": "^0.25.7", "micromatch": "^4.0.4", "npm-packlist": "^3.0.0", "react": "^17.0.2", @@ -92,6 +93,9 @@ "semver": "^7.3.5", "spdx-license-list": "^6.4.0" }, + "devDependencies": { + "@types/acorn": "^4.0.6" + }, "peerDependencies": { "chokidar": "^3.5.1", "typescript": "^4.2.4" diff --git a/packages/packemon/src/Package.ts b/packages/packemon/src/Package.ts index c62a4e5f3..a15153ce8 100644 --- a/packages/packemon/src/Package.ts +++ b/packages/packemon/src/Package.ts @@ -470,6 +470,14 @@ export class Package { const files = new Set(this.packageJson.files); + try { + if (this.path.append('assets').exists()) { + files.add('assets/**/*'); + } + } catch { + // May throw ENOENT + } + this.artifacts.forEach((artifact) => { // Build files if (artifact instanceof CodeArtifact) { diff --git a/packages/packemon/src/Packemon.ts b/packages/packemon/src/Packemon.ts index f0b8a22ca..a8e96123e 100644 --- a/packages/packemon/src/Packemon.ts +++ b/packages/packemon/src/Packemon.ts @@ -101,7 +101,7 @@ export class Packemon { await this.cleanTemporaryFiles(packages); // Clean build formats - const formatFolders = '{cjs,dts,esm,lib,mjs,umd}'; + const formatFolders = '{assets,cjs,dts,esm,lib,mjs,umd}'; const pathsToRemove: string[] = []; if (this.project.isWorkspacesEnabled()) { diff --git a/packages/packemon/src/constants.ts b/packages/packemon/src/constants.ts index bfeee8e97..00c3058a6 100644 --- a/packages/packemon/src/constants.ts +++ b/packages/packemon/src/constants.ts @@ -9,6 +9,36 @@ import { Support, } from './types'; +export const ASSETS = [ + // Styles + '.css', + '.scss', + '.sass', + '.less', + // Images + '.svg', + '.png', + '.jpg', + '.jpeg', + '.gif', + '.webp', + // Audio + '.ogg', + '.mp3', + '.mpe', + '.mpeg', + '.wav', + // Video + '.mp4', + '.mov', + '.avi', + '.webm', + // Fonts + '.woff', + '.woff2', + '.ttf', +]; + export const EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.cjs', '.mjs']; export const EXCLUDE = [ diff --git a/packages/packemon/src/rollup/config.ts b/packages/packemon/src/rollup/config.ts index 620184142..0648c91fd 100644 --- a/packages/packemon/src/rollup/config.ts +++ b/packages/packemon/src/rollup/config.ts @@ -11,6 +11,7 @@ import type { CodeArtifact } from '../CodeArtifact'; import { EXCLUDE, EXTENSIONS } from '../constants'; import { FeatureFlags, Format } from '../types'; import { addBinShebang } from './plugins/addBinShebang'; +import { copyAndRefAssets } from './plugins/copyAndRefAssets'; const sharedPlugins = [ resolve({ extensions: EXTENSIONS, preferBuiltins: true }), @@ -103,7 +104,7 @@ export function getRollupOutputConfig( // Map our externals to local paths with trailing extension paths: getRollupPaths(artifact, ext), // Use our extension for file names - assetFileNames: '../assets/[name]-[hash][extname]', + assetFileNames: 'assets/[name].[ext]', chunkFileNames: `${artifact.bundle ? 'bundle' : '[name]'}-[hash].${ext}`, entryFileNames: `[name].${ext}`, preserveModules: !artifact.bundle, @@ -166,6 +167,10 @@ export function getRollupConfig(artifact: CodeArtifact, features: FeatureFlags): }), // Externals MUST be listed before shared plugins ...sharedPlugins, + // Copy assets and update import references + copyAndRefAssets({ + dir: artifact.package.path.append('assets').path(), + }), // Declare Babel here so we can parse TypeScript/Flow getBabelInputPlugin({ ...getBabelInputConfig(artifact, features), diff --git a/packages/packemon/src/rollup/plugins/copyAndRefAssets.ts b/packages/packemon/src/rollup/plugins/copyAndRefAssets.ts new file mode 100644 index 000000000..51674337f --- /dev/null +++ b/packages/packemon/src/rollup/plugins/copyAndRefAssets.ts @@ -0,0 +1,197 @@ +import { createHash } from 'crypto'; +import path from 'path'; +import type { Node } from 'acorn'; +import fs from 'fs-extra'; +import MagicString from 'magic-string'; +import rimraf from 'rimraf'; +import { Plugin } from 'rollup'; +import { VirtualPath } from '@boost/common'; +import { ASSETS } from '../../constants'; + +function isAsset(id: string): boolean { + return ASSETS.some((ext) => id.endsWith(ext)); +} + +function isRequireStatement(node: CallExpression): boolean { + return ( + node && + node.type === 'CallExpression' && + node.callee && + node.callee.type === 'Identifier' && + node.callee.name === 'require' && + node.arguments.length > 0 + ); +} + +export interface CopyAssetsPlugin { + dir: string; +} + +export function copyAndRefAssets({ dir }: CopyAssetsPlugin): Plugin { + const assetsToCopy: Record = {}; + + function determineNewAsset(source: string, importer?: string): VirtualPath { + const id = new VirtualPath(importer ? path.dirname(importer) : '', source); + const ext = id.ext(); + const name = id.name(true); + + // Generate a hash of the source file path, + // and have it match between nix and windows + const hash = createHash('sha256') + .update(id.path().replace(new VirtualPath(path.dirname(dir)).path(), '')) + .digest('hex') + .slice(0, 8); + + // Create a new path that points to the assets folder + const newId = new VirtualPath(dir, `${name}-${hash}${ext}`); + + assetsToCopy[id.path()] = newId; + + return newId; + } + + return { + name: 'packemon-copy-and-ref-assets', + + // Delete old assets to remove any possible stale assets + async buildStart() { + await new Promise((resolve, reject) => { + rimraf(dir, (error) => { + if (error) { + reject(error); + } else { + resolve(undefined); + } + }); + }); + }, + + // Find assets and mark as external + resolveId(source) { + if (isAsset(source)) { + return { id: source, external: true }; + } + + return null; + }, + + // Update import/require declarations to new asset paths + renderChunk(code, chunk, options) { + let ast: ProgramNode; + + try { + ast = this.parse(code) as ProgramNode; + } catch { + // Unknown syntax may fail parsing, not much we can do here? + return null; + } + + const parentId = chunk.facadeModuleId!; // This correct? + const magicString = new MagicString(code); + let hasChanged = false; + + ast.body.forEach((node) => { + let source: Literal | undefined; + + // import './styles.css'; + if (node.type === 'ImportDeclaration') { + ({ source } = node); + + // require('./styles.css'); + } else if (node.type === 'ExpressionStatement' && isRequireStatement(node.expression)) { + source = node.expression.arguments[0]; + + // const foo = require('./styles.css'); + } else if ( + node.type === 'VariableDeclaration' && + node.declarations.length > 0 && + isRequireStatement(node.declarations[0].init) + ) { + source = node.declarations[0].init.arguments[0]; + } + + // Update to new path + if (source?.value && isAsset(source.value)) { + const newId = determineNewAsset(source.value, parentId); + + const importPath = options.preserveModules + ? new VirtualPath(path.relative(path.dirname(parentId), newId.path())).path() + : `../assets/${newId.name()}`; + + hasChanged = true; + magicString.overwrite(source.start, source.end, `'${importPath}'`); + } + }); + + if (!hasChanged) { + return null; + } + + return { + // eslint-disable-next-line @typescript-eslint/no-base-to-string + code: magicString.toString(), + map: null, + }; + }, + + // Copy all found assets + async generateBundle() { + // Only create the folder if we have assets to copy, + // otherwise it throws off `files` and other detection! + if (Object.keys(assetsToCopy).length > 0) { + await fs.mkdir(dir, { recursive: true }); + } + + // We don't use `assetFileNames` as we want a single assets folder + // at the root of the package, which Rollup does not allow. It wants + // multiple asset folders within each format! + await Promise.all( + Object.entries(assetsToCopy).map(async ([oldId, newId]) => { + if (!newId.exists()) { + await fs.copyFile(oldId, newId.path()); + } + }), + ); + }, + }; +} + +interface Literal extends Node { + value: string; +} + +interface Identifier extends Node { + type: 'Identifier'; + name: string; +} + +interface ImportDeclaration extends Node { + type: 'ImportDeclaration'; + source: Literal; +} + +interface ExpressionStatement extends Node { + type: 'ExpressionStatement'; + expression: CallExpression; +} + +interface CallExpression extends Node { + type: 'CallExpression'; + callee: Identifier; + arguments: Literal[]; +} + +interface VariableDeclaration extends Node { + type: 'VariableDeclaration'; + declarations: VariableDeclarator[]; +} + +interface VariableDeclarator extends Node { + type: 'VariableDeclarator'; + id: Identifier; + init: CallExpression; +} + +interface ProgramNode extends Node { + body: (ExpressionStatement | ImportDeclaration | VariableDeclaration)[]; +} diff --git a/packages/packemon/tests/Packemon.test.ts b/packages/packemon/tests/Packemon.test.ts index 2331bd70e..c040b0fa0 100644 --- a/packages/packemon/tests/Packemon.test.ts +++ b/packages/packemon/tests/Packemon.test.ts @@ -131,7 +131,10 @@ describe('Packemon', () => { it('cleans build folders from project', async () => { await packemon.clean(); - expect(rimraf).toHaveBeenCalledWith('./{cjs,dts,esm,lib,mjs,umd}', expect.any(Function)); + expect(rimraf).toHaveBeenCalledWith( + './{assets,cjs,dts,esm,lib,mjs,umd}', + expect.any(Function), + ); }); }); @@ -157,14 +160,17 @@ describe('Packemon', () => { await packemon.clean(); expect(rimraf).toHaveBeenCalledWith( - 'packages/*/{cjs,dts,esm,lib,mjs,umd}', + 'packages/*/{assets,cjs,dts,esm,lib,mjs,umd}', + expect.any(Function), + ); + expect(rimraf).toHaveBeenCalledWith( + 'other/{assets,cjs,dts,esm,lib,mjs,umd}', expect.any(Function), ); expect(rimraf).toHaveBeenCalledWith( - 'other/{cjs,dts,esm,lib,mjs,umd}', + 'misc/{assets,cjs,dts,esm,lib,mjs,umd}', expect.any(Function), ); - expect(rimraf).toHaveBeenCalledWith('misc/{cjs,dts,esm,lib,mjs,umd}', expect.any(Function)); }); }); }); diff --git a/packages/packemon/tests/__snapshots__/outputs.test.ts.snap b/packages/packemon/tests/__snapshots__/outputs.test.ts.snap index 798ff9b18..3ffa4e6f4 100644 --- a/packages/packemon/tests/__snapshots__/outputs.test.ts.snap +++ b/packages/packemon/tests/__snapshots__/outputs.test.ts.snap @@ -386,6 +386,207 @@ Array [ ] `; +exports[`Outputs bundle with assets bundles all files and references assets 1`] = ` +Array [ + "lib/index.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: stable, Format: lib +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +require('../assets/globals-107ab52e.css'); + +require('../assets/fonts-4e5dc96c.css'); + +require('../assets/styles-b11c3a83.css'); + +function button() {} + +exports.button = button; +", +] +`; + +exports[`Outputs bundle with assets bundles all files and references assets 2`] = ` +Array [ + "package.json", + Object { + "main": "./lib/index.js", + "name": "project-assets", + "packemon": Object { + "bundle": true, + "platform": "node", + }, + }, +] +`; + +exports[`Outputs bundle with assets bundles all files and references assets 3`] = ` +Array [ + "src/button/styles.css", + "assets/styles-b11c3a83.css", +] +`; + +exports[`Outputs bundle with assets bundles all files and references assets 4`] = ` +Array [ + "src/globals.css", + "assets/globals-107ab52e.css", +] +`; + +exports[`Outputs bundle with assets bundles all files and references assets 5`] = ` +Array [ + "src/shared/fonts.css", + "assets/fonts-4e5dc96c.css", +] +`; + +exports[`Outputs bundle with assets uses same assets across multiple formats 1`] = ` +Array [ + "cjs/index.cjs", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: stable, Format: cjs +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +require('../assets/globals-107ab52e.css'); + +require('../assets/fonts-4e5dc96c.css'); + +require('../assets/styles-b11c3a83.css'); + +function button() {} + +exports.button = button; +", +] +`; + +exports[`Outputs bundle with assets uses same assets across multiple formats 2`] = ` +Array [ + "esm/index.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: stable, Format: esm +import '../assets/globals-107ab52e.css'; +import '../assets/fonts-4e5dc96c.css'; +import '../assets/styles-b11c3a83.css'; + +function button() {} + +export { button }; +", +] +`; + +exports[`Outputs bundle with assets uses same assets across multiple formats 3`] = ` +Array [ + "lib/index.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: stable, Format: lib +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +require('../assets/globals-107ab52e.css'); + +require('../assets/fonts-4e5dc96c.css'); + +require('../assets/styles-b11c3a83.css'); + +function button() {} + +exports.button = button; +", +] +`; + +exports[`Outputs bundle with assets uses same assets across multiple formats 4`] = ` +Array [ + "package.json", + Object { + "main": "./lib/index.js", + "module": "./esm/index.js", + "name": "project-assets", + "packemon": Object { + "bundle": true, + "platform": "node", + }, + }, +] +`; + +exports[`Outputs bundle with assets uses same assets across multiple formats 5`] = ` +Array [ + "src/button/styles.css", + "assets/styles-b11c3a83.css", +] +`; + +exports[`Outputs bundle with assets uses same assets across multiple formats 6`] = ` +Array [ + "src/button/styles.css", + "assets/styles-b11c3a83.css", +] +`; + +exports[`Outputs bundle with assets uses same assets across multiple formats 7`] = ` +Array [ + "src/button/styles.css", + "assets/styles-b11c3a83.css", +] +`; + +exports[`Outputs bundle with assets uses same assets across multiple formats 8`] = ` +Array [ + "src/globals.css", + "assets/globals-107ab52e.css", +] +`; + +exports[`Outputs bundle with assets uses same assets across multiple formats 9`] = ` +Array [ + "src/globals.css", + "assets/globals-107ab52e.css", +] +`; + +exports[`Outputs bundle with assets uses same assets across multiple formats 10`] = ` +Array [ + "src/globals.css", + "assets/globals-107ab52e.css", +] +`; + +exports[`Outputs bundle with assets uses same assets across multiple formats 11`] = ` +Array [ + "src/shared/fonts.css", + "assets/fonts-4e5dc96c.css", +] +`; + +exports[`Outputs bundle with assets uses same assets across multiple formats 12`] = ` +Array [ + "src/shared/fonts.css", + "assets/fonts-4e5dc96c.css", +] +`; + +exports[`Outputs bundle with assets uses same assets across multiple formats 13`] = ` +Array [ + "src/shared/fonts.css", + "assets/fonts-4e5dc96c.css", +] +`; + exports[`Outputs no bundle creates individual files for every source file 1`] = ` Array [ "lib/index.js", @@ -450,3 +651,76 @@ Array [ }, ] `; + +exports[`Outputs no bundle with assets creates individual files and references assets 1`] = ` +Array [ + "lib/button/index.js", + "'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +require('../../assets/styles-b11c3a83.css'); + +function button() {} + +exports.button = button; +", +] +`; + +exports[`Outputs no bundle with assets creates individual files and references assets 2`] = ` +Array [ + "lib/index.js", + "'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +require('../assets/globals-107ab52e.css'); + +require('../assets/fonts-4e5dc96c.css'); + +const index = require('./button/index.js'); + +exports.button = index.button; +", +] +`; + +exports[`Outputs no bundle with assets creates individual files and references assets 3`] = ` +Array [ + "package.json", + Object { + "main": "./lib/index.js", + "name": "project-assets", + "packemon": Object { + "bundle": true, + "platform": "node", + }, + }, +] +`; + +exports[`Outputs no bundle with assets creates individual files and references assets 4`] = ` +Array [ + "src/button/styles.css", + "assets/styles-b11c3a83.css", +] +`; + +exports[`Outputs no bundle with assets creates individual files and references assets 5`] = ` +Array [ + "src/globals.css", + "assets/globals-107ab52e.css", +] +`; + +exports[`Outputs no bundle with assets creates individual files and references assets 6`] = ` +Array [ + "src/shared/fonts.css", + "assets/fonts-4e5dc96c.css", +] +`; diff --git a/packages/packemon/tests/examples/__snapshots__/assetImports.test.ts.snap b/packages/packemon/tests/examples/__snapshots__/assetImports.test.ts.snap new file mode 100644 index 000000000..e11d5f961 --- /dev/null +++ b/packages/packemon/tests/examples/__snapshots__/assetImports.test.ts.snap @@ -0,0 +1,849 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Assets transforms example test case: browser-current-esm 1`] = ` +Array [ + "esm/index-browser-current-esm.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: browser, Support: current, Format: esm +import '../assets/styles-17df299b.css'; +import '../assets/logo-c21f559f.svg'; +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: browser-current-esm 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: browser-current-lib 1`] = ` +Array [ + "lib/index-browser-current-lib.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: browser, Support: current, Format: lib +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: browser-current-lib 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: browser-current-umd 1`] = ` +Array [ + "umd/index-browser-current-umd.js", + "(function (global, factory) { + if (typeof define === \\"function\\" && define.amd) { + define([\\"./styles.css\\", \\"./logo.svg\\"], factory); + } else if (typeof exports !== \\"undefined\\") { + factory(require(\\"./styles.css\\"), require(\\"./logo.svg\\")); + } else { + var mod = { + exports: {} + }; + factory(global.styles, global.logo); + global.examples = mod.exports; + } +})(typeof globalThis !== \\"undefined\\" ? globalThis : typeof self !== \\"undefined\\" ? self : this, function (_styles, _logo) { + \\"use strict\\"; + + // Bundled with Packemon: https://packemon.dev + // Platform: browser, Support: current, Format: umd + const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } + }; + console.log(json); +}); +", +] +`; + +exports[`Assets transforms example test case: browser-experimental-esm 1`] = ` +Array [ + "esm/index-browser-experimental-esm.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: browser, Support: experimental, Format: esm +import '../assets/styles-17df299b.css'; +import '../assets/logo-c21f559f.svg'; +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: browser-experimental-esm 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: browser-experimental-lib 1`] = ` +Array [ + "lib/index-browser-experimental-lib.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: browser, Support: experimental, Format: lib +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: browser-experimental-lib 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: browser-experimental-umd 1`] = ` +Array [ + "umd/index-browser-experimental-umd.js", + "(function (global, factory) { + if (typeof define === \\"function\\" && define.amd) { + define([\\"./styles.css\\", \\"./logo.svg\\"], factory); + } else if (typeof exports !== \\"undefined\\") { + factory(require(\\"./styles.css\\"), require(\\"./logo.svg\\")); + } else { + var mod = { + exports: {} + }; + factory(global.styles, global.logo); + global.examples = mod.exports; + } +})(typeof globalThis !== \\"undefined\\" ? globalThis : typeof self !== \\"undefined\\" ? self : this, function (_styles, _logo) { + \\"use strict\\"; + + // Bundled with Packemon: https://packemon.dev + // Platform: browser, Support: experimental, Format: umd + const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } + }; + console.log(json); +}); +", +] +`; + +exports[`Assets transforms example test case: browser-legacy-esm 1`] = ` +Array [ + "esm/index-browser-legacy-esm.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: browser, Support: legacy, Format: esm +import '../assets/styles-17df299b.css'; +import '../assets/logo-c21f559f.svg'; +var json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: browser-legacy-esm 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: browser-legacy-lib 1`] = ` +Array [ + "lib/index-browser-legacy-lib.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: browser, Support: legacy, Format: lib +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +var json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: browser-legacy-lib 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: browser-legacy-umd 1`] = ` +Array [ + "umd/index-browser-legacy-umd.js", + "(function (global, factory) { + if (typeof define === \\"function\\" && define.amd) { + define([\\"./styles.css\\", \\"./logo.svg\\"], factory); + } else if (typeof exports !== \\"undefined\\") { + factory(require(\\"./styles.css\\"), require(\\"./logo.svg\\")); + } else { + var mod = { + exports: {} + }; + factory(global.styles, global.logo); + global.examples = mod.exports; + } +})(typeof globalThis !== \\"undefined\\" ? globalThis : typeof self !== \\"undefined\\" ? self : this, function (_styles, _logo) { + \\"use strict\\"; + + // Bundled with Packemon: https://packemon.dev + // Platform: browser, Support: legacy, Format: umd + var json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } + }; + console.log(json); +}); +", +] +`; + +exports[`Assets transforms example test case: browser-stable-esm 1`] = ` +Array [ + "esm/index-browser-stable-esm.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: browser, Support: stable, Format: esm +import '../assets/styles-17df299b.css'; +import '../assets/logo-c21f559f.svg'; +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: browser-stable-esm 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: browser-stable-lib 1`] = ` +Array [ + "lib/index-browser-stable-lib.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: browser, Support: stable, Format: lib +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: browser-stable-lib 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: browser-stable-umd 1`] = ` +Array [ + "umd/index-browser-stable-umd.js", + "(function (global, factory) { + if (typeof define === \\"function\\" && define.amd) { + define([\\"./styles.css\\", \\"./logo.svg\\"], factory); + } else if (typeof exports !== \\"undefined\\") { + factory(require(\\"./styles.css\\"), require(\\"./logo.svg\\")); + } else { + var mod = { + exports: {} + }; + factory(global.styles, global.logo); + global.examples = mod.exports; + } +})(typeof globalThis !== \\"undefined\\" ? globalThis : typeof self !== \\"undefined\\" ? self : this, function (_styles, _logo) { + \\"use strict\\"; + + // Bundled with Packemon: https://packemon.dev + // Platform: browser, Support: stable, Format: umd + const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } + }; + console.log(json); +}); +", +] +`; + +exports[`Assets transforms example test case: native-current-lib 1`] = ` +Array [ + "lib/index-native-current-lib.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: native, Support: current, Format: lib +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: native-current-lib 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: native-experimental-lib 1`] = ` +Array [ + "lib/index-native-experimental-lib.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: native, Support: experimental, Format: lib +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: native-experimental-lib 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: native-legacy-lib 1`] = ` +Array [ + "lib/index-native-legacy-lib.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: native, Support: legacy, Format: lib +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +var json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: native-legacy-lib 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: native-stable-lib 1`] = ` +Array [ + "lib/index-native-stable-lib.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: native, Support: stable, Format: lib +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: native-stable-lib 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: node-current-cjs 1`] = ` +Array [ + "cjs/index-node-current-cjs.cjs", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: current, Format: cjs +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: node-current-cjs 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: node-current-lib 1`] = ` +Array [ + "lib/index-node-current-lib.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: current, Format: lib +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: node-current-lib 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: node-current-mjs 1`] = ` +Array [ + "mjs/index-node-current-mjs.mjs", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: current, Format: mjs +import '../assets/styles-17df299b.css'; +import '../assets/logo-c21f559f.svg'; +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: node-current-mjs 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: node-experimental-cjs 1`] = ` +Array [ + "cjs/index-node-experimental-cjs.cjs", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: experimental, Format: cjs +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: node-experimental-cjs 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: node-experimental-lib 1`] = ` +Array [ + "lib/index-node-experimental-lib.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: experimental, Format: lib +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: node-experimental-lib 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: node-experimental-mjs 1`] = ` +Array [ + "mjs/index-node-experimental-mjs.mjs", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: experimental, Format: mjs +import '../assets/styles-17df299b.css'; +import '../assets/logo-c21f559f.svg'; +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: node-experimental-mjs 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: node-legacy-cjs 1`] = ` +Array [ + "cjs/index-node-legacy-cjs.cjs", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: legacy, Format: cjs +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +var json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: node-legacy-cjs 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: node-legacy-lib 1`] = ` +Array [ + "lib/index-node-legacy-lib.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: legacy, Format: lib +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +var json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: node-legacy-lib 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: node-legacy-mjs 1`] = ` +Array [ + "mjs/index-node-legacy-mjs.mjs", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: legacy, Format: mjs +import '../assets/styles-17df299b.css'; +import '../assets/logo-c21f559f.svg'; +var json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: node-legacy-mjs 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: node-stable-cjs 1`] = ` +Array [ + "cjs/index-node-stable-cjs.cjs", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: stable, Format: cjs +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: node-stable-cjs 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: node-stable-lib 1`] = ` +Array [ + "lib/index-node-stable-lib.js", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: stable, Format: lib +'use strict'; + +require('../assets/styles-17df299b.css'); + +require('../assets/logo-c21f559f.svg'); + +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: node-stable-lib 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; + +exports[`Assets transforms example test case: node-stable-mjs 1`] = ` +Array [ + "mjs/index-node-stable-mjs.mjs", + "// Bundled with Packemon: https://packemon.dev +// Platform: node, Support: stable, Format: mjs +import '../assets/styles-17df299b.css'; +import '../assets/logo-c21f559f.svg'; +const json = { + list: [\\"foo\\", \\"bar\\", \\"baz\\"], + number: 123, + string: \\"abc\\", + object: { + foo: true + } +}; +console.log(json); +", +] +`; + +exports[`Assets transforms example test case: node-stable-mjs 2`] = ` +Array [ + "styles.css", + "assets/styles-17df299b.css", +] +`; diff --git a/packages/packemon/tests/examples/assetImports.test.ts b/packages/packemon/tests/examples/assetImports.test.ts new file mode 100644 index 000000000..52a459ebb --- /dev/null +++ b/packages/packemon/tests/examples/assetImports.test.ts @@ -0,0 +1,5 @@ +import { testExampleOutput } from '../helpers'; + +describe('Assets', () => { + testExampleOutput('asset-imports.ts'); +}); diff --git a/packages/packemon/tests/helpers.ts b/packages/packemon/tests/helpers.ts index 73cef9316..98a8c66b6 100644 --- a/packages/packemon/tests/helpers.ts +++ b/packages/packemon/tests/helpers.ts @@ -57,18 +57,21 @@ export function createProjectPackage(root: Path, customProject?: Project): Packa ); } +function formatSnapshotFilePath(file: string, root: string): string { + return new Path(String(file)) + .path() + .replace(String(root), '') + .replace(/^(\/|\\)/, '') + .replace(/\\/g, '/'); +} + export function createSnapshotSpies(root: PortablePath, captureJson: boolean = false) { let snapshots: [string, unknown][] = []; - let fsSpy: jest.SpyInstance; - let fsxSpy: jest.SpyInstance; - let warnSpy: jest.SpyInstance; + const spies: jest.SpyInstance[] = []; beforeEach(() => { const handler = (file: unknown, content: unknown, cb?: unknown) => { - const filePath = new Path(String(file)) - .path() - .replace(String(root), '') - .replace(/^(\/|\\)/, ''); + const filePath = formatSnapshotFilePath(String(file), String(root)); if ( filePath.endsWith('.js') || @@ -79,21 +82,27 @@ export function createSnapshotSpies(root: PortablePath, captureJson: boolean = f snapshots.push([filePath, content]); } + if (filePath.endsWith('.css')) { + snapshots.push([filePath, formatSnapshotFilePath(String(content), String(root))]); + } + if (typeof cb === 'function') { cb(null); } }; - fsSpy = jest.spyOn(fs, 'writeFile').mockImplementation(handler); - fsxSpy = jest.spyOn(fsx, 'writeJson').mockImplementation(handler); - warnSpy = jest.spyOn(console, 'warn').mockImplementation(); + spies.push( + jest.spyOn(fs, 'writeFile').mockImplementation(handler), + jest.spyOn(fsx, 'writeJson').mockImplementation(handler), + jest.spyOn(fsx, 'copyFile').mockImplementation(handler), + jest.spyOn(fsx, 'mkdir'), + jest.spyOn(console, 'warn').mockImplementation(), + ); }); afterEach(() => { snapshots = []; - fsSpy.mockRestore(); - fsxSpy.mockRestore(); - warnSpy.mockRestore(); + spies.forEach((spy) => void spy.mockRestore()); }); return (pkg: Package) => { diff --git a/packages/packemon/tests/outputs.test.ts b/packages/packemon/tests/outputs.test.ts index df5e32d09..fd4e3050c 100644 --- a/packages/packemon/tests/outputs.test.ts +++ b/packages/packemon/tests/outputs.test.ts @@ -77,6 +77,58 @@ describe('Outputs', () => { }); }); + describe('bundle with assets', () => { + const root = new Path(getFixturePath('project-assets')); + const snapshots = createSnapshotSpies(root, true); + + it('bundles all files and references assets', async () => { + const pkg = createProjectPackage(root); + + const index = new CodeArtifact(pkg, [{ format: 'lib' }]); + index.bundle = true; + index.platform = 'node'; + index.support = 'stable'; + index.inputs = { index: 'src/index.ts' }; + + pkg.addArtifact(index); + + await pkg.build({}); + + snapshots(pkg).forEach((ss) => { + expect(ss).toMatchSnapshot(); + + // Check import paths are correct + if (ss[0].endsWith('index.js')) { + expect(String(ss[1])).toContain("'../assets/globals-107ab52e.css'"); + expect(String(ss[1])).toContain("'../assets/fonts-4e5dc96c.css'"); + expect(String(ss[1])).toContain("'../assets/styles-b11c3a83.css'"); + } + }); + }); + + it('uses same assets across multiple formats', async () => { + const pkg = createProjectPackage(root); + + const index = new CodeArtifact(pkg, [ + { format: 'lib' }, + { format: 'esm' }, + { format: 'cjs' }, + ]); + index.bundle = true; + index.platform = 'node'; + index.support = 'stable'; + index.inputs = { index: 'src/index.ts' }; + + pkg.addArtifact(index); + + await pkg.build({}); + + snapshots(pkg).forEach((ss) => { + expect(ss).toMatchSnapshot(); + }); + }); + }); + describe('no bundle', () => { const root = new Path(getFixturePath('project-bundle')); const snapshots = createSnapshotSpies(root, true); @@ -99,4 +151,37 @@ describe('Outputs', () => { }); }); }); + + describe('no bundle with assets', () => { + const root = new Path(getFixturePath('project-assets')); + const snapshots = createSnapshotSpies(root, true); + + it('creates individual files and references assets', async () => { + const pkg = createProjectPackage(root); + + const index = new CodeArtifact(pkg, [{ format: 'lib' }]); + index.bundle = false; + index.platform = 'node'; + index.support = 'stable'; + index.inputs = { index: 'src/index.ts' }; + + pkg.addArtifact(index); + + await pkg.build({}); + + snapshots(pkg).forEach((ss) => { + expect(ss).toMatchSnapshot(); + + // Check import paths are correct + if (ss[0].endsWith('lib/index.js')) { + expect(String(ss[1])).toContain("'../assets/globals-107ab52e.css'"); + expect(String(ss[1])).toContain("'../assets/fonts-4e5dc96c.css'"); + } + + if (ss[0].endsWith('lib/button/index.js')) { + expect(String(ss[1])).toContain("'../../assets/styles-b11c3a83.css'"); + } + }); + }); + }); }); diff --git a/packages/packemon/tests/rollup/config.test.ts b/packages/packemon/tests/rollup/config.test.ts index 152b7e206..67cddd4a0 100644 --- a/packages/packemon/tests/rollup/config.test.ts +++ b/packages/packemon/tests/rollup/config.test.ts @@ -57,6 +57,7 @@ describe('getRollupConfig()', () => { 'resolve()', 'commonjs()', 'json()', + expect.any(Object), `babelInput(${fixturePath})`, ]; @@ -118,7 +119,7 @@ describe('getRollupConfig()', () => { }, output: [ { - assetFileNames: '../assets/[name]-[hash][extname]', + assetFileNames: 'assets/[name].[ext]', banner: expect.any(String), chunkFileNames: 'bundle-[hash].js', dir: fixturePath.append('lib').path(), @@ -136,7 +137,7 @@ describe('getRollupConfig()', () => { sourcemapExcludeSources: true, }, { - assetFileNames: '../assets/[name]-[hash][extname]', + assetFileNames: 'assets/[name].[ext]', banner: expect.any(String), chunkFileNames: 'bundle-[hash].js', dir: fixturePath.append('esm').path(), @@ -153,7 +154,7 @@ describe('getRollupConfig()', () => { sourcemapExcludeSources: true, }, { - assetFileNames: '../assets/[name]-[hash][extname]', + assetFileNames: 'assets/[name].[ext]', banner: expect.any(String), chunkFileNames: 'bundle-[hash].mjs', dir: fixturePath.append('mjs').path(), @@ -189,7 +190,7 @@ describe('getRollupConfig()', () => { }, output: [ { - assetFileNames: '../assets/[name]-[hash][extname]', + assetFileNames: 'assets/[name].[ext]', banner: expect.any(String), chunkFileNames: 'bundle-[hash].js', dir: fixturePath.append('lib').path(), @@ -228,7 +229,7 @@ describe('getRollupConfig()', () => { ].map((f) => fixturePath.append(f).path()), output: [ { - assetFileNames: '../assets/[name]-[hash][extname]', + assetFileNames: 'assets/[name].[ext]', chunkFileNames: '[name]-[hash].js', dir: fixturePath.append('lib').path(), entryFileNames: '[name].js', @@ -327,7 +328,7 @@ describe('getRollupOutputConfig()', () => { it('generates default output config', () => { expect(getRollupOutputConfig(artifact, {}, 'lib')).toEqual({ - assetFileNames: '../assets/[name]-[hash][extname]', + assetFileNames: 'assets/[name].[ext]', banner: expect.any(String), chunkFileNames: 'bundle-[hash].js', dir: fixturePath.append('lib').path(), @@ -510,7 +511,7 @@ describe('getRollupOutputConfig()', () => { artifact.namespace = 'FooBar'; expect(getRollupOutputConfig(artifact, {}, 'umd')).toEqual({ - assetFileNames: '../assets/[name]-[hash][extname]', + assetFileNames: 'assets/[name].[ext]', banner: expect.any(String), chunkFileNames: 'bundle-[hash].js', dir: fixturePath.append('umd').path(), diff --git a/tests/__fixtures__/examples/asset-imports.ts b/tests/__fixtures__/examples/asset-imports.ts new file mode 100644 index 000000000..aace80d1b --- /dev/null +++ b/tests/__fixtures__/examples/asset-imports.ts @@ -0,0 +1,5 @@ +import json from './data.json'; +import './styles.css'; +import './logo.svg'; + +console.log(json); diff --git a/tests/__fixtures__/examples/logo.svg b/tests/__fixtures__/examples/logo.svg new file mode 100644 index 000000000..e3194ea69 --- /dev/null +++ b/tests/__fixtures__/examples/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/tests/__fixtures__/examples/styles.css b/tests/__fixtures__/examples/styles.css new file mode 100644 index 000000000..74e7f69fe --- /dev/null +++ b/tests/__fixtures__/examples/styles.css @@ -0,0 +1,2 @@ +.baz { +} diff --git a/tests/__fixtures__/project-assets/package.json b/tests/__fixtures__/project-assets/package.json new file mode 100644 index 000000000..b25d2cf7a --- /dev/null +++ b/tests/__fixtures__/project-assets/package.json @@ -0,0 +1,8 @@ +{ + "name": "project-assets", + "packemon": { + "bundle": true, + "platform": "node" + }, + "main": "./lib/index.js" +} diff --git a/tests/__fixtures__/project-assets/src/button/index.ts b/tests/__fixtures__/project-assets/src/button/index.ts new file mode 100644 index 000000000..8303253ed --- /dev/null +++ b/tests/__fixtures__/project-assets/src/button/index.ts @@ -0,0 +1,3 @@ +import './styles.css'; + +export function button() {} diff --git a/tests/__fixtures__/project-assets/src/button/styles.css b/tests/__fixtures__/project-assets/src/button/styles.css new file mode 100644 index 000000000..f54e640d9 --- /dev/null +++ b/tests/__fixtures__/project-assets/src/button/styles.css @@ -0,0 +1,2 @@ +.button { +} diff --git a/tests/__fixtures__/project-assets/src/globals.css b/tests/__fixtures__/project-assets/src/globals.css new file mode 100644 index 000000000..5d82ecb83 --- /dev/null +++ b/tests/__fixtures__/project-assets/src/globals.css @@ -0,0 +1,2 @@ +html { +} diff --git a/tests/__fixtures__/project-assets/src/index.ts b/tests/__fixtures__/project-assets/src/index.ts new file mode 100644 index 000000000..18129e70e --- /dev/null +++ b/tests/__fixtures__/project-assets/src/index.ts @@ -0,0 +1,4 @@ +import './globals.css'; +import './shared/fonts.css'; + +export { button } from './button'; diff --git a/tests/__fixtures__/project-assets/src/shared/fonts.css b/tests/__fixtures__/project-assets/src/shared/fonts.css new file mode 100644 index 000000000..0a3e45c7b --- /dev/null +++ b/tests/__fixtures__/project-assets/src/shared/fonts.css @@ -0,0 +1,2 @@ +@font-face { +} diff --git a/website/docs/features.md b/website/docs/features.md index 4160f8be6..ba5281539 100644 --- a/website/docs/features.md +++ b/website/docs/features.md @@ -120,6 +120,30 @@ Make use of `import()` and Packemon will ensure proper code-splitting for consum persist dynamic imports when the the target platform and supported version can utilize the feature natively, otherwise it is transpiled down. +## Asset imports + +When a file imports an asset (styles, images, audio, video), the import remains in-tact so any +bundlers can handle accordingly. However, assets are moved to a shared `assets` folder, are hashed +for uniqueness, and any imports are modified to this new path. + +An example of this as follows: + +```ts +// Input: +// src/components/Button/index.ts +// src/components/Button/button.css +import './button.css'; +``` + +```ts +// Output: +// src/components/Button/index.ts +// assets/button-as172k9.css +import '../../../assets/button-as172k9.css'; +``` + +> [View full list of supported assets.](https://github.com/milesj/packemon/blob/master/packages/packemon/src/constants.ts#L12) + ## Environment constants The [babel-plugin-env-constants](https://www.npmjs.com/package/babel-plugin-env-constants) plugin is diff --git a/yarn.lock b/yarn.lock index 6cac5b850..d44fa7c24 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4444,6 +4444,15 @@ __metadata: languageName: node linkType: hard +"@types/acorn@npm:^4.0.6": + version: 4.0.6 + resolution: "@types/acorn@npm:4.0.6" + dependencies: + "@types/estree": "*" + checksum: 60e1fd28af18d6cb54a93a7231c7c18774a9a8739c9b179e9e8750dca631e10cbef2d82b02830ea3f557b1d121e6406441e9e1250bd492dc81d4b3456e76e4d4 + languageName: node + linkType: hard + "@types/argparse@npm:1.0.38": version: 1.0.38 resolution: "@types/argparse@npm:1.0.38" @@ -12931,11 +12940,11 @@ __metadata: linkType: hard "mime@npm:^2.3.1": - version: 2.5.2 - resolution: "mime@npm:2.5.2" + version: 2.6.0 + resolution: "mime@npm:2.6.0" bin: mime: cli.js - checksum: dd3c93d433d41a09f6a1cfa969b653b769899f3bd573e7bfcea33bdc8b0cc4eba57daa2f95937369c2bd2b6d39d62389b11a4309fe40d1d3a1b736afdedad0ff + checksum: 1497ba7b9f6960694268a557eae24b743fd2923da46ec392b042469f4b901721ba0adcf8b0d3c2677839d0e243b209d76e5edcbd09cfdeffa2dfb6bb4df4b862 languageName: node linkType: hard @@ -14114,6 +14123,7 @@ __metadata: "@rollup/plugin-commonjs": ^21.0.1 "@rollup/plugin-json": ^4.1.0 "@rollup/plugin-node-resolve": ^13.1.3 + "@types/acorn": ^4.0.6 babel-plugin-cjs-esm-interop: ^1.2.1 babel-plugin-conditional-invariant: ^1.1.1 babel-plugin-env-constants: ^1.1.1 @@ -14126,6 +14136,7 @@ __metadata: ink: ^3.2.0 ink-progress-bar: ^3.0.0 ink-spinner: ^4.0.3 + magic-string: ^0.25.7 micromatch: ^4.0.4 npm-packlist: ^3.0.0 react: ^17.0.2