From e79b184f4109ef49cce4788ef49ead9f8acb8eb7 Mon Sep 17 00:00:00 2001 From: CoolElectronics Date: Tue, 9 Apr 2024 21:28:08 -0400 Subject: [PATCH] rewrite build system and bundles --- dreamland.d.ts | 95 +++++++++++++++++ package.json | 28 ++--- pnpm-lock.yaml | 33 ++++++ rollup.config.mjs | 184 ++++++++++++++++++++------------ src/{js.js => core.js} | 16 +-- src/css.js | 1 - src/dev.js | 12 --- src/{html.js => jsxLiterals.js} | 3 +- src/main.js | 26 +++++ src/{store.js => stores.js} | 3 +- 10 files changed, 291 insertions(+), 110 deletions(-) create mode 100644 dreamland.d.ts rename src/{js.js => core.js} (97%) delete mode 100644 src/dev.js rename src/{html.js => jsxLiterals.js} (96%) create mode 100644 src/main.js rename src/{store.js => stores.js} (79%) diff --git a/dreamland.d.ts b/dreamland.d.ts new file mode 100644 index 0000000..4e8ab9e --- /dev/null +++ b/dreamland.d.ts @@ -0,0 +1,95 @@ +declare namespace JSX { + export type IntrinsicElements = { + [index: string]: any + } + type ElementType = Fragment | string | Component + type Element = DLElement + + interface ElementAttributesProperty { + props: {} + } + interface ElementChildrenAttribute { + children: {} + } +} + +declare function h( + type: string, + props?: { [index: string]: any } | null, + ...children: (HTMLElement | string)[] +): Node +declare function $if( + condition: DLPointer | any, + then?: Element, + otherwise?: Element +): HTMLElement + +type DLPointer = { + readonly __symbol: unique symbol + readonly __signature: T +} + +declare function use(sink: T, mapping?: (arg: T) => any): DLPointer +declare function useValue(trap: DLPointer): T + +type Stateful = T & { readonly symbol: unique symbol } + +declare function stateful(target: T): Stateful +declare function $state(target: T): Stateful +declare function $store( + target: T, + ident: string, + backing: 'localstorage' +): Stateful + +declare function handle( + references: DLPointer, + callback: (value: T) => void +): void + +declare function css(strings: TemplateStringsArray, ...values: any): string + +type DLCSS = string + +declare var $el: HTMLElement + +type Fragment = { readonly fragment: unique symbol } +declare var Fragment: Fragment + +interface Element { + $: OuterComponentTypes & { [index: string | symbol]: any } +} + +interface DLElement extends HTMLElement { + $: T & OuterComponentTypes +} + +type ComponentElement any> = ReturnType + +type OuterComponentTypes = { + root: Element + children: Element[] +} +type InnerComponentTypes = { + css: DLCSS + mount?: () => void +} +type ComponentTypes = OuterComponentTypes & InnerComponentTypes + +type ArrayOrSingular = T | T[keyof T] + +type Component< + Public, + Private, + Constructed extends string | symbol | number = never, +> = ( + this: Public & Private & ComponentTypes, + props: ([Constructed] extends [never] + ? Public + : Omit) & { + children?: ArrayOrSingular< + Private extends { children: any } ? Private['children'] : never + > + [index: `${'bind:'}${string}`]: any + } +) => DLElement diff --git a/package.json b/package.json index 3779584..216be58 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,7 @@ "version": "0.0.6", "description": "A utilitarian HTML rendering library", "scripts": { - "build": "rollup -c && prettier -w .", - "watch": "rollup -cw", + "build": "rollup -c --dloutput dist/minimal.js --disable-css --disable-jsxLiterals --disable-usestring --disable-stores && rollup -c --dloutput dist/dev.js --dev --enable-css --enable-jsxLiterals --enable-usestring --enable-stores && rollup -c --dloutput dist/all.js --enable-css --enable-jsxLiterals --enable-usestring --enable-stores && prettier -w .", "publish": "npm publish --access public" }, "keywords": [ @@ -17,28 +16,20 @@ "author": "MercuryWorkshop", "repository": "https://github.com/MercuryWorkshop/dreamlandjs", "license": "MIT", - "browser": "./dist/dev/index.js", + "browser": "./dist/all.js", "types": "./dreamland.d.ts", - "node": "./dist/dev/index.js", + "node": "./dist/all.js", "exports": { "./dev": { - "default": "./dist/dev/index.js", + "default": "./dist/dev.js", "types": "./dreamland.d.ts" }, - "./js": { - "default": "./dist/js.js", + "./minimal": { + "default": "./dist/minimal.js", "types": "./dreamland.d.ts" }, - "./css": { - "default": "./dist/css.js", - "types": "./dreamland.d.ts" - }, - "./html": { - "default": "./dist/html.js", - "types": "./dreamland.d.ts" - }, - "./store": { - "default": "./dist/store.js", + ".": { + "default": "./dist/all.js", "types": "./dreamland.d.ts" } }, @@ -52,5 +43,8 @@ "prettier": "^3.2.5", "rollup": "^4.13.0", "typescript": "^5.4.2" + }, + "dependencies": { + "rollup-plugin-strip-code": "^0.2.7" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 661e4e1..9cf32a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +dependencies: + rollup-plugin-strip-code: + specifier: ^0.2.7 + version: 0.2.7 + devDependencies: '@rollup/plugin-strip': specifier: ^3.0.4 @@ -227,6 +232,10 @@ packages: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} dev: true + /estree-walker@0.6.1: + resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} + dev: false + /estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} dev: true @@ -239,6 +248,12 @@ packages: dev: true optional: true + /magic-string@0.25.3: + resolution: {integrity: sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA==} + dependencies: + sourcemap-codec: 1.4.8 + dev: false + /magic-string@0.30.8: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} engines: {node: '>=12'} @@ -263,6 +278,19 @@ packages: safe-buffer: 5.2.1 dev: true + /rollup-plugin-strip-code@0.2.7: + resolution: {integrity: sha512-+5t9u/VrHPSfiRWWKMVin+KOtFwFak337FAZxeTjxYDjB3DDoHBQRkXHQvBn713eAfW81t41mGuysqsMXiuTjw==} + dependencies: + magic-string: 0.25.3 + rollup-pluginutils: 2.8.1 + dev: false + + /rollup-pluginutils@2.8.1: + resolution: {integrity: sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg==} + dependencies: + estree-walker: 0.6.1 + dev: false + /rollup@4.13.0: resolution: {integrity: sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -312,6 +340,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /sourcemap-codec@1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead + dev: false + /terser@5.29.2: resolution: {integrity: sha512-ZiGkhUBIM+7LwkNjXYJq8svgkd+QK3UUr0wJqY4MieaezBSAIPgbSPZyIx0idM6XWK5CMzSWa8MJIzmRcB8Caw==} engines: {node: '>=10'} diff --git a/rollup.config.mjs b/rollup.config.mjs index 0fefc9c..2afabc2 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,74 +1,118 @@ import strip from '@rollup/plugin-strip' import terser from '@rollup/plugin-terser' +import stripCode from 'rollup-plugin-strip-code' -const prodPlugins = [ - strip({ - functions: ['console.log', 'assert.*', 'panic', 'log'], - labels: ['dev'], - }), - terser({ - mangle: { - toplevel: true, +export default (args) => { + const plugins = [] + + const stripfunctions = [] + + if (!args.dev) { + stripfunctions.push('console.log', 'assert.*', 'panic', 'log') + } + + plugins.push( + strip({ + functions: stripfunctions, + sourceMap: true, + }) + ) + + if (!args.dev) { + plugins.push( + stripCode({ + start_comment: 'DEV.START', + end_comment: 'DEV.END', + }) + ) + } + + const features = { + css: true, + jsxLiterals: false, + usestring: false, + stores: false, + } + + for (const arg in args) { + if (arg.startsWith('disable-')) { + const feature = arg.slice(8) + features[feature] = false + } + if (arg.startsWith('enable-')) { + const feature = arg.slice(7) + features[feature] = true + } + } + + for (const [feature, enabled] of Object.entries(features)) { + if (!enabled) { + plugins.push( + stripCode({ + start_comment: `FEATURE.${feature.toUpperCase()}.START`, + end_comment: `FEATURE.${feature.toUpperCase()}.END`, + }) + ) + } + } + + const dlbanner = `// dreamland.js, MIT license\nconst DLFEATURES = [${Object.entries( + features + ) + .filter(([_, enabled]) => enabled) + .map(([feature, _]) => `'${feature}'`) + .join(', ')}];` + if (args.dev || args.nominify) { + plugins.push({ + banner() { + return dlbanner + }, + }) + } else { + plugins.push( + terser({ + mangle: { + toplevel: true, + }, + compress: { + unused: true, + collapse_vars: true, + toplevel: true, + }, + output: { + comments: false, + preamble: dlbanner, + }, + }) + ) + } + + const sharedOutput = {} + + const iifeOutput = { + format: 'iife', + name: 'window', + extend: true, + sourcemap: true, + ...sharedOutput, + } + + const devOutput = { + format: 'cjs', + sourcemap: true, + ...sharedOutput, + } + + const output = args.dev ? devOutput : iifeOutput + + return [ + { + input: 'src/main.js', + output: { + file: args.dloutput, + ...output, + }, + plugins: plugins, }, - compress: { - unused: true, - collapse_vars: true, - toplevel: true, - }, - output: { - comments: false, - }, - }), -] -const devPlugins = [] -export default [ - { - input: 'src/dev.js', - output: { - file: 'dist/dev/index.js', - format: 'cjs', - sourcemap: true, - }, - plugins: devPlugins, - }, - { - input: 'src/js.js', - output: { - file: 'dist/js.js', - strict: false, - format: 'cjs', - sourcemap: true, - }, - plugins: prodPlugins, - }, - { - input: 'src/css.js', - output: { - file: 'dist/css.js', - strict: false, - format: 'cjs', - sourcemap: true, - }, - plugins: prodPlugins, - }, - { - input: 'src/html.js', - output: { - file: 'dist/html.js', - strict: false, - format: 'cjs', - sourcemap: true, - }, - plugins: prodPlugins, - }, - { - input: 'src/store.js', - output: { - file: 'dist/store.js', - strict: false, - format: 'cjs', - sourcemap: true, - }, - plugins: prodPlugins, - }, -] + ] +} diff --git a/src/js.js b/src/core.js similarity index 97% rename from src/js.js rename to src/core.js index 6b43282..636a04b 100644 --- a/src/js.js +++ b/src/core.js @@ -33,7 +33,9 @@ Object.defineProperty(window, 'use', { get: () => { __use_trap = true return (ptr, mapping, ...rest) => { + /* FEATURE.USESTRING.START */ if (ptr instanceof Array) return usestr(ptr, mapping, ...rest) + /* FEATURE.USESTRING.END */ assert( isDLPtr(ptr), 'a value was passed into use() that was not part of a stateful context' @@ -44,6 +46,8 @@ Object.defineProperty(window, 'use', { } }, }) + +/* FEATURE.USESTRING.START */ const usestr = (strings, ...values) => { __use_trap = false @@ -73,14 +77,14 @@ const usestr = (strings, ...values) => { return use(state.string) } -Object.assign(window, { isDLPtr, h, stateful, handle, $if, Fragment }) +/* FEATURE.USESTRING.END */ let TRAPS = new Map() // This wraps the target in a proxy, doing 2 things: // - whenever a property is accessed, return a "trap" that catches and records accessors // - whenever a property is set, notify the subscribed listeners // This is what makes our "pass-by-reference" magic work -function stateful(target, hook) { +export function stateful(target, hook) { assert(isobj(target), 'stateful() requires an object') target[LISTENERS] = [] target[TARGET] = target @@ -136,11 +140,11 @@ function stateful(target, hook) { let isobj = (o) => o instanceof Object let isfn = (o) => typeof o === 'function' -function isDLPtr(arr) { +export function isDLPtr(arr) { return isobj(arr) && TARGET in arr } -function $if(condition, then, otherwise) { +export function $if(condition, then, otherwise) { otherwise ??= document.createTextNode('') if (!isDLPtr(condition)) return condition ? then : otherwise @@ -148,7 +152,7 @@ function $if(condition, then, otherwise) { } // This lets you subscribe to a stateful object -function handle(ptr, callback) { +export function handle(ptr, callback) { assert(isDLPtr(ptr), 'handle() requires a stateful object') assert(isfn(callback), 'handle() requires a callback function') let step, @@ -239,7 +243,7 @@ let curryset = (ptr) => (val) => { } // Actual JSX factory. Responsible for creating the HTML elements and all of the *reactive* syntactic sugar -function h(type, props, ...children) { +export function h(type, props, ...children) { if (type == Fragment) return children if (typeof type == 'function') { // functional components. create the stateful object diff --git a/src/css.js b/src/css.js index 1df96e4..cb423e7 100644 --- a/src/css.js +++ b/src/css.js @@ -1,4 +1,3 @@ -Object.assign(window, { css, styled: { new: css, rule: css } }) const cssmap = {} export function css(strings, ...values) { diff --git a/src/dev.js b/src/dev.js deleted file mode 100644 index 63b10a2..0000000 --- a/src/dev.js +++ /dev/null @@ -1,12 +0,0 @@ -import { log } from './asserts' -import { VERSION } from './consts' - -import './js' -import './css' -import './html' -import './store' - -log('Version: ' + VERSION) -console.warn( - 'This is a DEVELOPER build of dreamland.js. It is not suitable for production use.' -) diff --git a/src/html.js b/src/jsxLiterals.js similarity index 96% rename from src/html.js rename to src/jsxLiterals.js index bded48f..b0c7cb3 100644 --- a/src/html.js +++ b/src/jsxLiterals.js @@ -1,5 +1,4 @@ -Object.assign(window, { html }) -function html(strings, ...values) { +export function html(strings, ...values) { let flattened = '' let markers = {} for (const i in strings) { diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..fe63ea8 --- /dev/null +++ b/src/main.js @@ -0,0 +1,26 @@ +import { VERSION } from './consts' +export { VERSION as DLVERSION } + +export * from './core' + +/* FEATURE.CSS.START */ +export * from './css' +/* FEATURE.CSS.END */ + +/* FEATURE.JSXLITERALS.START */ +export * from './jsxLiterals' +/* FEATURE.JSXLITERALS.END */ + +/* FEATURE.STORES.START */ +export * from './stores' +/* FEATURE.STORES.END */ + +/* DEV.START */ +import { log } from './asserts' + +log('Version: ' + VERSION) +console.warn( + 'This is a DEVELOPER build of dreamland.js. It is not suitable for production use.' +) +console.info('Enabled features:', DLFEATURES.join(', ')) +/* DEV.END */ diff --git a/src/store.js b/src/stores.js similarity index 79% rename from src/store.js rename to src/stores.js index 861a0f8..4e42365 100644 --- a/src/store.js +++ b/src/stores.js @@ -1,5 +1,4 @@ -Object.assign(window, { $store }) -function $store(target, ident, type) { +export function $store(target, ident, type) { let stored = localStorage.getItem(ident) target = JSON.parse(stored) ?? target