rewrite build system and bundles

This commit is contained in:
CoolElectronics 2024-04-09 21:28:08 -04:00
parent f6367de472
commit e79b184f41
No known key found for this signature in database
GPG key ID: F63593D168636C50
10 changed files with 291 additions and 110 deletions

95
dreamland.d.ts vendored Normal file
View file

@ -0,0 +1,95 @@
declare namespace JSX {
export type IntrinsicElements = {
[index: string]: any
}
type ElementType = Fragment | string | Component<any, any>
type Element = DLElement<any>
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> | any,
then?: Element,
otherwise?: Element
): HTMLElement
type DLPointer<T> = {
readonly __symbol: unique symbol
readonly __signature: T
}
declare function use<T>(sink: T, mapping?: (arg: T) => any): DLPointer<T>
declare function useValue<T>(trap: DLPointer<T>): T
type Stateful<T> = T & { readonly symbol: unique symbol }
declare function stateful<T>(target: T): Stateful<T>
declare function $state<T>(target: T): Stateful<T>
declare function $store<T>(
target: T,
ident: string,
backing: 'localstorage'
): Stateful<T>
declare function handle<T>(
references: DLPointer<T>,
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<T> extends HTMLElement {
$: T & OuterComponentTypes
}
type ComponentElement<T extends (...args: any) => any> = ReturnType<T>
type OuterComponentTypes = {
root: Element
children: Element[]
}
type InnerComponentTypes = {
css: DLCSS
mount?: () => void
}
type ComponentTypes = OuterComponentTypes & InnerComponentTypes
type ArrayOrSingular<T extends []> = T | T[keyof T]
type Component<
Public,
Private,
Constructed extends string | symbol | number = never,
> = (
this: Public & Private & ComponentTypes,
props: ([Constructed] extends [never]
? Public
: Omit<Public, Constructed>) & {
children?: ArrayOrSingular<
Private extends { children: any } ? Private['children'] : never
>
[index: `${'bind:'}${string}`]: any
}
) => DLElement<Public>

View file

@ -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"
}
}

33
pnpm-lock.yaml generated
View file

@ -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'}

View file

@ -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,
},
]
]
}

View file

@ -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

View file

@ -1,4 +1,3 @@
Object.assign(window, { css, styled: { new: css, rule: css } })
const cssmap = {}
export function css(strings, ...values) {

View file

@ -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.'
)

View file

@ -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) {

26
src/main.js Normal file
View file

@ -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 */

View file

@ -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