From 059af8f5378888c0a1f146c05550bcfbbae5e932 Mon Sep 17 00:00:00 2001 From: Maycon Sousa Date: Tue, 30 Aug 2022 10:32:21 -0300 Subject: [PATCH 1/9] add FC type --- types/ClientContext.d.ts | 22 ++++++++++---------- types/JSX.d.ts | 45 ++++++++++++++++++++-------------------- types/index.d.ts | 22 ++++++++++++++++++++ 3 files changed, 55 insertions(+), 34 deletions(-) diff --git a/types/ClientContext.d.ts b/types/ClientContext.d.ts index 6a343c5e..ce6163bd 100644 --- a/types/ClientContext.d.ts +++ b/types/ClientContext.d.ts @@ -105,7 +105,7 @@ export type NullstackClientContext = TProps & { * * @see https://nullstack.app/two-way-bindings#complex-bindable-components */ - bind?: { property?: string | number, object?: any }; + bind?: { property?: string | number; object?: any }; /** * Bind value. @@ -115,16 +115,16 @@ export type NullstackClientContext = TProps & { value?: any; /** - * Ref object. - * - * @see https://nullstack.app/refs#complex-refable-components - */ - ref?: { property?: string | number, object?: any } | ((context: NullstackClientContext) => void); + * Ref object. + * + * @see https://nullstack.app/refs#complex-refable-components + */ + ref?: { property?: string | number; object?: any } | ((context: NullstackClientContext) => void); /** - * Ref element. - * - * @see https://nullstack.app/refs#complex-refable-components - */ - element?: HTMLElement + * Ref element. + * + * @see https://nullstack.app/refs#complex-refable-components + */ + element?: HTMLElement; }; diff --git a/types/JSX.d.ts b/types/JSX.d.ts index 2c012656..69a6ceee 100644 --- a/types/JSX.d.ts +++ b/types/JSX.d.ts @@ -57,7 +57,7 @@ export interface Attributes { [key: string]: any; } -export interface NullstackAttributes extends Attributes { } +export interface NullstackAttributes extends Attributes {} export interface ClassAttributes extends Attributes { key?: string; @@ -69,7 +69,7 @@ export interface ClassAttributes extends Attributes { type DetailedHTMLFactory = P; -export interface SVGFactory { } +export interface SVGFactory {} export type NullstackFragment = NullstackNode[]; export type NullstackNode = NullstackFragment | string | number | boolean | null | undefined; @@ -101,7 +101,7 @@ export interface BaseSyntheticEvent { * This might be a child element to the element on which the event listener is registered. * If you thought this should be `EventTarget & T`, see https://github.com/DefinitelyTyped/DefinitelyTyped/issues/11508#issuecomment-256045682 */ -export interface SyntheticEvent extends BaseSyntheticEvent { } +export interface SyntheticEvent extends BaseSyntheticEvent {} export interface DragEvent extends MouseEvent { dataTransfer: DataTransfer; @@ -126,7 +126,7 @@ export interface FocusEvent target: EventTarget & Target; } -export interface FormEvent extends SyntheticEvent { } +export interface FormEvent extends SyntheticEvent {} export interface ChangeEvent extends SyntheticEvent { target: EventTarget & T; @@ -196,8 +196,8 @@ export interface WheelEvent extends MouseEvent type EventHandler> = | object | { - bivarianceHack(event: { event: E } & NullstackClientContext): void; - }['bivarianceHack']; + bivarianceHack(event: { event: E } & NullstackClientContext): void; + }['bivarianceHack']; type NullstackEventHandler = EventHandler>; type DragEventHandler = EventHandler>; @@ -216,7 +216,7 @@ type WheelEventHandler = EventHandler>; type DetailedHTMLProps, T> = E; -export interface SVGProps extends SVGAttributes, ClassAttributes { } +export interface SVGProps extends SVGAttributes, ClassAttributes {} export interface DOMAttributes extends Attributes { // Focus Events @@ -305,7 +305,7 @@ export interface DOMAttributes extends Attributes { // All the WAI-ARIA 1.1 attributes from https://www.w3.org/TR/wai-aria-1.1/ export interface AriaAttributes { - /** Identifies the currently active element when DOM focus is on a composite widget, textbox, group, or applicatio */ + /** Identifies the currently active element when DOM focus is on a composite widget, textbox, group, or application */ 'aria-activedescendant'?: string; /** Indicates whether assistive technologies will present all, or only parts of, the changed region based on the change notifications defined by the aria-relevant attribute. */ 'aria-atomic'?: Booleanish; @@ -447,17 +447,16 @@ export interface AriaAttributes { * @see aria-atomic. */ 'aria-relevant'?: - | 'additions' - | 'additions removals' - | 'additions text' - | 'all' - | 'removals' - | 'removals additions' - | 'removals text' - | 'text' - | 'text additions' - | 'text removals' - | undefined; + | 'additions' + | 'additions removals' + | 'additions text' + | 'all' + | 'removals' + | 'removals additions' + | 'removals text' + | 'text' + | 'text additions' + | 'text removals'; /** Indicates that user input is required on the element before a form may be submitted. */ 'aria-required'?: Booleanish; /** Defines a human-readable, author-localized description for the role of an element. */ @@ -745,7 +744,7 @@ export interface AnchorHTMLAttributes extends HTMLAttributes { path?: string; } -export interface AudioHTMLAttributes extends MediaHTMLAttributes { } +export interface AudioHTMLAttributes extends MediaHTMLAttributes {} export interface AreaHTMLAttributes extends HTMLAttributes { alt?: string; @@ -1237,8 +1236,8 @@ declare global { namespace JSX { type Element = NullstackNode; - interface IntrinsicAttributes extends NullstackAttributes { } - interface IntrinsicClassAttributes extends ClassAttributes { } + interface IntrinsicAttributes extends NullstackAttributes {} + interface IntrinsicClassAttributes extends ClassAttributes {} interface AllElements { // HTML @@ -1423,6 +1422,6 @@ declare global { element: ElementTagHTMLAttributes; } - interface IntrinsicElements extends ExoticElements, AllElements { } + interface IntrinsicElements extends ExoticElements, AllElements {} } } diff --git a/types/index.d.ts b/types/index.d.ts index dfc26037..b3c6125a 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -17,6 +17,28 @@ export * from './Settings'; export * from './Worker'; export * from './JSX'; +/** + * Functional component + * + * @example + * + * ``` + * import { FC, NullstackClientContext } from 'nullstack'; + * + * interface InputProps { + * label: string; + * } + * + * function Input({ label, page }: NullstackClientContext) { + * console.log(page.title); + * return

{label}

; + * } + * + * export default Input as FC; + * ``` + */ +export type FC = (props: TProps) => NullstackNode; + export default class Nullstack { constructor(props?: TProps); From 6fa1fae00411d4fce1ce293797bb0ddd59c7f14a Mon Sep 17 00:00:00 2001 From: Maycon Sousa Date: Tue, 30 Aug 2022 17:34:18 -0300 Subject: [PATCH 2/9] Rename from FC to NullstackFunctionalComponent --- types/index.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index b3c6125a..361ffb09 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -23,7 +23,7 @@ export * from './JSX'; * @example * * ``` - * import { FC, NullstackClientContext } from 'nullstack'; + * import { NullstackClientContext, NullstackFunctionalComponent } from 'nullstack'; * * interface InputProps { * label: string; @@ -34,10 +34,10 @@ export * from './JSX'; * return

{label}

; * } * - * export default Input as FC; + * export default Input as NullstackFunctionalComponent; * ``` */ -export type FC = (props: TProps) => NullstackNode; +export type NullstackFunctionalComponent = (props: TProps) => NullstackNode; export default class Nullstack { constructor(props?: TProps); From bd2d21116e485e5eb6c8d513a80e8d3eb00fefea Mon Sep 17 00:00:00 2001 From: Christian Mortaro Date: Wed, 30 Nov 2022 11:20:04 -0300 Subject: [PATCH 3/9] :sparkles: exposed server functions --- server/server.js | 31 ++++++++++++ tests/server.js | 8 ++++ tests/src/Application.njs | 2 + tests/src/ExposedServerFunctions.jsx | 60 ++++++++++++++++++++++++ tests/src/ExposedServerFunctions.test.js | 43 +++++++++++++++++ 5 files changed, 144 insertions(+) create mode 100644 tests/src/ExposedServerFunctions.jsx create mode 100644 tests/src/ExposedServerFunctions.test.js diff --git a/server/server.js b/server/server.js index 919b1f83..7a3c5ed9 100644 --- a/server/server.js +++ b/server/server.js @@ -18,6 +18,7 @@ import { generateServiceWorker } from './worker'; import reqres from './reqres' import WebSocket from 'ws'; import { writeFileSync } from 'fs' +import extractParamValue from '../shared/extractParamValue'; if (!global.fetch) { global.fetch = fetch; @@ -30,6 +31,36 @@ server.port = process.env['NULSTACK_SERVER_PORT_YOU_SHOULD_NOT_CARE_ABOUT'] || p let contextStarted = false let serverStarted = false +for (const method of ['get', 'post', 'put', 'patch', 'delete', 'all']) { + const original = server[method].bind(server) + server[method] = function (...args) { + if (typeof args[1] === 'function' && args[1].name === '_invoke') { + original(args[0], bodyParser.text({ limit: server.maximumPayloadSize }), async (request, response) => { + const params = {} + for (const key of Object.keys(request.params)) { + params[key] = extractParamValue(request.params[key]) + } + for (const key of Object.keys(request.query)) { + params[key] = extractParamValue(request.query[key]) + } + if (request.method !== 'GET') { + Object.assign(params, deserialize(request.body)); + } + try { + const context = generateContext({ request, response, ...params }); + const result = await args[1](context); + response.json(result); + } catch (error) { + printError(error); + response.status(500).json({}); + } + }) + } else { + original(...args) + } + } +} + server.use(async (request, response, next) => { if (!contextStarted) { typeof context.start === 'function' && await context.start(); diff --git a/tests/server.js b/tests/server.js index b8c05793..08393331 100644 --- a/tests/server.js +++ b/tests/server.js @@ -4,6 +4,7 @@ import ContextProject from './src/ContextProject'; import ContextSecrets from './src/ContextSecrets'; import ContextSettings from './src/ContextSettings'; import ContextWorker from './src/ContextWorker'; +import ExposedServerFunctions from './src/ExposedServerFunctions'; import vueable from './src/plugins/vueable'; import ServerRequestAndResponse from './src/ServerRequestAndResponse'; @@ -13,6 +14,13 @@ const context = Nullstack.start(Application); const methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT']; +context.server.get('/data/all/:param', ExposedServerFunctions.getData) +context.server.get('/data/get/:param', ExposedServerFunctions.getData) +context.server.post('/data/post/:param', ExposedServerFunctions.getData) +context.server.put('/data/put/:param', ExposedServerFunctions.getData) +context.server.patch('/data/patch/:param', ExposedServerFunctions.getData) +context.server.delete('/data/delete/:param', ExposedServerFunctions.getData) + context.server.get('/custom-api-before-start', (request, response) => { response.json({ startValue: context.startValue }) }) diff --git a/tests/src/Application.njs b/tests/src/Application.njs index fd6afdec..70930d3f 100644 --- a/tests/src/Application.njs +++ b/tests/src/Application.njs @@ -54,6 +54,7 @@ import BodyFragment from './BodyFragment'; import ArrayAttributes from './ArrayAttributes'; import RouteScroll from './RouteScroll'; import IsomorphicImport from './IsomorphicImport.njs'; +import ExposedServerFunctions from './ExposedServerFunctions'; class Application extends Nullstack { @@ -135,6 +136,7 @@ class Application extends Nullstack { + ) diff --git a/tests/src/ExposedServerFunctions.jsx b/tests/src/ExposedServerFunctions.jsx new file mode 100644 index 00000000..cea35bb0 --- /dev/null +++ b/tests/src/ExposedServerFunctions.jsx @@ -0,0 +1,60 @@ +import Nullstack from 'nullstack'; + +async function request(method) { + const body = { + number: 69, + date: new Date(), + string: 'nullstack' + } + const response = await fetch(`/data/${method}/param?query=query&truthy=true&falsy=false`, { + method: method.toUpperCase(), + body: method === 'get' ? undefined : JSON.stringify(body) + }) + const data = await response.json() + return data.status +} + +class ExposedServerFunctions extends Nullstack { + + static async getData({ request, project, param, query, truthy, falsy, number, date, string }) { + return { + status: ( + param === 'param' + && query === 'query' + && truthy === true + && falsy === false + && project.name === 'Nullstack Tests' + && (request.originalUrl.includes('/get/') || ( + number === 69 + && date.getFullYear() === new Date().getFullYear() + && string === 'nullstack' + )) + ) + } + } + + async hydrate() { + this.all = await request('get') + this.get = await request('get') + this.post = await request('post') + this.put = await request('put') + this.patch = await request('patch') + this.delete = await request('delete') + } + + render() { + return ( +
+ ) + } + +} + +export default ExposedServerFunctions; \ No newline at end of file diff --git a/tests/src/ExposedServerFunctions.test.js b/tests/src/ExposedServerFunctions.test.js new file mode 100644 index 00000000..117ec08e --- /dev/null +++ b/tests/src/ExposedServerFunctions.test.js @@ -0,0 +1,43 @@ +beforeAll(async () => { + await page.goto('http://localhost:6969/exposed-server-functions'); +}); + +describe('ExposedServerFunctions', () => { + + test('server functions can be exposed to GET and serialize params and query', async () => { + await page.waitForSelector('[data-get]'); + const element = await page.$('[data-get]'); + expect(element).toBeTruthy(); + }); + + test('server functions can be exposed to POST and serialize params and query and body', async () => { + await page.waitForSelector('[data-post]'); + const element = await page.$('[data-post]'); + expect(element).toBeTruthy(); + }); + + test('server functions can be exposed to PUT and serialize params and query and body', async () => { + await page.waitForSelector('[data-put]'); + const element = await page.$('[data-put]'); + expect(element).toBeTruthy(); + }); + + test('server functions can be exposed to PATCH and serialize params and query and body', async () => { + await page.waitForSelector('[data-patch]'); + const element = await page.$('[data-patch]'); + expect(element).toBeTruthy(); + }); + + test('server functions can be exposed to DELETE and serialize params and query and body', async () => { + await page.waitForSelector('[data-delete]'); + const element = await page.$('[data-delete]'); + expect(element).toBeTruthy(); + }); + + test('server functions can be exposed to ALL and serialize params and query and body', async () => { + await page.waitForSelector('[data-all]'); + const element = await page.$('[data-all]'); + expect(element).toBeTruthy(); + }); + +}); \ No newline at end of file From 7b39dfb4c781f2fc6671649adc34e98986e799e2 Mon Sep 17 00:00:00 2001 From: Christian Mortaro Date: Wed, 30 Nov 2022 14:29:15 -0300 Subject: [PATCH 4/9] :construction: maycon lint --- .eslintrc | 12 + .prettierrc | 7 + builders/spa.js | 29 +- builders/ssg.js | 89 +- builders/ssr.js | 15 +- client/client.js | 9 +- client/context.js | 27 +- client/environment.js | 8 +- client/events.js | 26 +- client/hydrate.js | 25 +- client/index.js | 136 +- client/instanceProxyHandler.js | 31 +- client/invoke.js | 58 +- client/objectProxyHandler.js | 28 +- client/page.js | 27 +- client/params.js | 46 +- client/project.js | 8 +- client/ref.js | 2 +- client/render.js | 48 +- client/rerender.js | 78 +- client/router.js | 96 +- client/segments.js | 10 +- client/settings.js | 10 +- client/state.js | 6 +- client/windowEvent.js | 12 +- client/worker.js | 82 +- loaders/add-source-to-node.js | 20 +- loaders/ignore-import.js | 10 +- loaders/inject-hmr.js | 20 +- loaders/inject-nullstack.js | 28 +- loaders/register-inner-components.js | 80 +- loaders/register-static-from-server.js | 44 +- loaders/remove-import-from-client.js | 45 +- loaders/remove-static-from-client.js | 68 +- loaders/string-replace.js | 16 +- loaders/transform-node-ref.js | 18 +- logo.tsx | 8 +- nullstack.js | 4 +- package.json | 9 +- plugins/anchorable.js | 6 +- plugins/bindable.js | 19 +- plugins/parameterizable.js | 38 +- plugins/routable.js | 31 +- scripts/index.js | 118 +- scripts/socket.js | 87 +- server/client.js | 18 +- server/configurable.js | 14 +- server/context.js | 13 +- server/dotenv.js | 4 +- server/environment.js | 16 +- server/files.js | 21 +- server/generator.js | 4 +- server/index.js | 84 +- server/instanceProxyHandler.js | 11 +- server/integrities.js | 9 +- server/links.js | 31 +- server/manifest.js | 61 +- server/params.js | 14 +- server/prerender.js | 90 +- server/printError.js | 44 +- server/project.js | 42 +- server/registry.js | 4 +- server/render.js | 71 +- server/renderAttributes.js | 21 +- server/reqres.js | 2 +- server/robots.js | 30 +- server/router.js | 24 +- server/secrets.js | 6 +- server/server.js | 272 +-- server/settings.js | 6 +- server/template.js | 55 +- server/worker.js | 106 +- shared/deserialize.js | 22 +- shared/element.js | 17 +- shared/extractLocation.js | 29 +- shared/extractParamValue.js | 8 +- shared/fragment.js | 6 +- shared/generateKey.js | 8 +- shared/generateTree.js | 129 +- shared/generateTruthyString.js | 7 +- shared/getProxyableMethods.js | 10 +- shared/getQueryStringParams.js | 19 +- shared/nodes.js | 13 +- shared/noop.js | 3 +- shared/plugins.js | 21 +- shared/prefix.js | 4 +- shared/routeMatches.js | 33 +- shared/sanitizeString.js | 17 +- shared/serializeParam.js | 4 +- shared/serializeSearch.js | 18 +- shared/string.js | 6 +- tests/client.js | 15 +- tests/jest-puppeteer.config.js | 14 +- tests/jest.config.js | 8 +- tests/package.json | 1 + tests/server.js | 53 +- tests/src/AnchorModifiers.njs | 14 +- tests/src/AnchorModifiers.test.js | 134 +- tests/src/Application.njs | 123 +- tests/src/Application.test.js | 32 +- tests/src/ArrayAttributes.njs | 58 +- tests/src/ArrayAttributes.test.js | 84 +- tests/src/BodyFragment.njs | 28 +- tests/src/BodyFragment.test.js | 78 +- tests/src/ChildComponent.test.js | 88 +- tests/src/ChildComponent.tsx | 22 +- tests/src/ComponentTernary.njs | 5 +- tests/src/ComponentTernary.test.js | 30 +- tests/src/Context.njs | 20 +- tests/src/Context.test.js | 86 +- tests/src/ContextData.njs | 8 +- tests/src/ContextData.test.js | 44 +- tests/src/ContextEnvironment.njs | 4 +- tests/src/ContextEnvironment.test.js | 60 +- tests/src/ContextPage.njs | 58 +- tests/src/ContextPage.test.js | 110 +- tests/src/ContextProject.njs | 32 +- tests/src/ContextProject.test.js | 190 +- tests/src/ContextSecrets.njs | 10 +- tests/src/ContextSecrets.test.js | 32 +- tests/src/ContextSettings.njs | 6 +- tests/src/ContextSettings.test.js | 32 +- tests/src/ContextWorker.njs | 42 +- tests/src/ContextWorker.test.js | 152 +- tests/src/DateParser.njs | 8 +- tests/src/DateParser.test.js | 36 +- tests/src/DynamicHead.njs | 53 +- tests/src/DynamicHead.test.js | 182 +- tests/src/Element.njs | 25 +- tests/src/Element.test.js | 45 +- tests/src/ErrorOnChildNode.njs | 153 +- tests/src/ErrorOnChildNode.test.js | 85 +- tests/src/ErrorPage.njs | 8 +- tests/src/ErrorPage.test.js | 60 +- tests/src/ExposedServerFunctions.jsx | 28 +- tests/src/ExposedServerFunctions.test.js | 56 +- tests/src/ExternalServerFunctions.njs | 7 +- tests/src/ExternalServerFunctions.test.js | 40 +- tests/src/FalsyNodes.njs | 16 +- tests/src/FalsyNodes.test.js | 38 +- tests/src/FullStackLifecycle.njs | 24 +- tests/src/FullStackLifecycle.test.js | 164 +- tests/src/InstanceKey.njs | 14 +- tests/src/InstanceKey.test.js | 34 +- tests/src/InstanceSelf.njs | 7 +- tests/src/InstanceSelf.test.js | 56 +- tests/src/Instanceable.njs | 66 +- tests/src/Instanceable.test.js | 50 +- tests/src/IsomorphicImport.njs | 12 +- tests/src/IsomorphicImport.test.js | 40 +- tests/src/IsomorphicStartup.njs | 8 +- tests/src/IsomorphicStartup.test.js | 40 +- tests/src/JavaScriptExtension.jsx | 10 +- tests/src/JavaScriptExtension.test.js | 16 +- tests/src/LazyComponent.njs | 11 +- tests/src/LazyComponent.test.js | 30 +- tests/src/LazyComponentLoader.njs | 5 +- tests/src/MetatagState.njs | 8 +- tests/src/MetatagState.test.js | 23 +- tests/src/NestedProxy.njs | 18 +- tests/src/NestedProxy.test.js | 104 +- tests/src/OptimizedEvents.njs | 54 +- tests/src/OptimizedEvents.test.js | 84 +- tests/src/ParentComponent.njs | 16 +- tests/src/ParentComponent.test.js | 40 +- tests/src/PersistentComponent.njs | 8 +- tests/src/PersistentComponent.test.js | 144 +- tests/src/PluginAttributes.njs | 45 +- tests/src/PluginAttributes.test.js | 54 +- tests/src/PublicServerFunctions.njs | 4 +- tests/src/PureComponents.njs | 17 +- tests/src/PureComponents.test.js | 80 +- tests/src/PureFunctions.njs | 12 +- tests/src/Refs.njs | 20 +- tests/src/Refs.test.js | 97 +- tests/src/RenderableComponent.njs | 30 +- tests/src/RenderableComponent.test.js | 164 +- tests/src/RouteScroll.njs | 13 +- tests/src/RoutesAndParams.njs | 61 +- tests/src/RoutesAndParams.test.js | 441 ++--- tests/src/ScriptRun.test.js | 12 +- tests/src/ServerFunctions.njs | 59 +- tests/src/ServerFunctions.test.js | 103 +- tests/src/ServerRequestAndResponse.njs | 22 +- tests/src/ServerRequestandResponse.test.js | 78 +- tests/src/StatefulComponent.njs | 46 +- tests/src/StatefulComponent.test.js | 130 +- tests/src/StaticThis.njs | 16 +- tests/src/StaticThis.test.js | 32 +- tests/src/TextObserver.njs | 27 +- tests/src/TextObserver.test.js | 26 +- tests/src/TwoWayBindings.njs | 75 +- tests/src/TwoWayBindings.test.js | 252 ++- tests/src/TwoWayBindingsExternalComponent.njs | 12 +- tests/src/TypeScript.nts | 19 +- tests/src/TypeScriptExtension.tsx | 6 +- tests/src/TypesScript.test.js | 42 +- tests/src/TypesScriptExtension.test.js | 24 +- tests/src/UndefinedNodes.njs | 10 +- tests/src/UndefinedNodes.test.js | 44 +- tests/src/UnderscoredAttributes.njs | 20 +- tests/src/UnderscoredAttributes.test.js | 60 +- tests/src/Vunerability.njs | 6 +- tests/src/Vunerability.test.js | 30 +- tests/src/WebpackCustomPlugin.njs | 8 +- tests/src/WebpackCustomPlugin.test.js | 18 +- tests/src/WindowDependency.js | 2 +- tests/src/WorkerVerbs.njs | 18 +- tests/src/WorkerVerbs.test.js | 64 +- tests/src/helpers.js | 6 +- tests/src/plugins/vueable.js | 34 +- tests/src/scripts/run.js | 9 +- tests/webpack.config.js | 26 +- types/ClientContext.d.ts | 63 +- types/Environment.d.ts | 20 +- types/JSX.d.ts | 1664 +++++++++-------- types/Page.d.ts | 25 +- types/Params.d.ts | 2 +- types/Plugin.d.ts | 24 +- types/Project.d.ts | 34 +- types/Router.d.ts | 12 +- types/Secrets.d.ts | 2 +- types/Server.d.ts | 24 +- types/ServerContext.d.ts | 33 +- types/Settings.d.ts | 2 +- types/Worker.d.ts | 9 +- types/index.d.ts | 70 +- webpack.config.js | 378 ++-- workers/activate.js | 22 +- workers/cacheFirst.js | 16 +- workers/dynamicFetch.js | 35 +- workers/dynamicInstall.js | 18 +- workers/load.js | 9 +- workers/networkDataFirst.js | 22 +- workers/networkFirst.js | 16 +- workers/staleWhileRevalidate.js | 22 +- workers/staticFetch.js | 41 +- workers/staticHelpers.js | 31 +- workers/staticInstall.js | 27 +- 239 files changed, 5883 insertions(+), 5752 deletions(-) create mode 100644 .eslintrc create mode 100644 .prettierrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..5ba5e3db --- /dev/null +++ b/.eslintrc @@ -0,0 +1,12 @@ +{ + "extends": "plugin:nullstack/recommended", + "plugins": [ + "jest" + ], + "env": { + "jest/globals": true + }, + "globals": { + "page": "writable" + } +} \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..729f927c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "singleQuote": true, + "printWidth": 120 +} \ No newline at end of file diff --git a/builders/spa.js b/builders/spa.js index 6904fc7d..730748f5 100644 --- a/builders/spa.js +++ b/builders/spa.js @@ -1,16 +1,17 @@ +/* eslint-disable no-console */ module.exports = async function spa({ output, cache, environment }) { - const folder = output || 'spa'; - process.env.NULLSTACK_ENVIRONMENT_MODE = 'spa'; + const folder = output || 'spa' + process.env.NULLSTACK_ENVIRONMENT_MODE = 'spa' - const dir = process.cwd(); - const application = require(`${dir}/.${environment}/server`).default; - const projectName = application.project.name || 'The Nullstack application'; - const { existsSync, mkdirSync, writeFileSync, copySync, removeSync } = require('fs-extra'); - const path = `${dir}/${folder}`; + const dir = process.cwd() + const application = require(`${dir}/.${environment}/server`).default + const projectName = application.project.name || 'The Nullstack application' + const { existsSync, mkdirSync, writeFileSync, copySync, removeSync } = require('fs-extra') + const path = `${dir}/${folder}` async function copy(url, file) { console.log(` ⚙️ ${file || url}`) - const content = await application.server.prerender(url); + const content = await application.server.prerender(url) const target = `${dir}/${folder}${file || url}` writeFileSync(target, content) } @@ -21,11 +22,11 @@ module.exports = async function spa({ output, cache, environment }) { console.log() if (existsSync(path)) { - removeSync(path); + removeSync(path) } mkdirSync(path) console.log(` ⚙️ /public/`) - copySync(`${dir}/public`, path); + copySync(`${dir}/public`, path) await copy('/', '/index.html') console.log(` ⚙️ /.${environment}/`) copySync(`${dir}/.${environment}`, path, { filter }) @@ -34,11 +35,11 @@ module.exports = async function spa({ output, cache, environment }) { await copy('/robots.txt') console.log() - console.log('\x1b[36m%s\x1b[0m', ` ✅️ ${projectName} is ready at ${folder}\n`); + console.log('\x1b[36m%s\x1b[0m', ` ✅️ ${projectName} is ready at ${folder}\n`) if (cache) { - console.log('Storing cache...'); + console.log('Storing cache...') } else if (environment === 'production') { - process.exit(); + process.exit() } -} \ No newline at end of file +} diff --git a/builders/ssg.js b/builders/ssg.js index dcd2156f..0f411a7d 100644 --- a/builders/ssg.js +++ b/builders/ssg.js @@ -1,55 +1,60 @@ +/* eslint-disable no-console */ module.exports = async function ssg({ output, cache, environment }) { - const folder = output || 'ssg'; - process.env.NULLSTACK_ENVIRONMENT_MODE = 'ssg'; + const folder = output || 'ssg' + process.env.NULLSTACK_ENVIRONMENT_MODE = 'ssg' - const dir = process.cwd(); - const application = require(`${dir}/.${environment}/server`).default; - const projectName = application.project.name || 'The Nullstack application'; + const dir = process.cwd() + const application = require(`${dir}/.${environment}/server`).default + const projectName = application.project.name || 'The Nullstack application' const { resolve } = require('path') - const { existsSync, mkdirSync, writeFileSync, copySync, removeSync } = require('fs-extra'); + const { existsSync, mkdirSync, writeFileSync, copySync, removeSync } = require('fs-extra') function path(file = '') { - const target = file.startsWith('/') ? file.slice(1) : file; + const target = file.startsWith('/') ? file.slice(1) : file return resolve(`${dir}/${folder}`, target).split('?')[0] } - const links = {}; - const pages = {}; + const links = {} + const pages = {} async function copyRoute(url = '/') { - links[url] = true; + links[url] = true if (url.indexOf('.') > -1) { - return; + return } - const content = await application.server.prerender(url); + const content = await application.server.prerender(url) const target = path(url) console.log(` ⚙️ ${url}`) if (!existsSync(target)) { - mkdirSync(target, { recursive: true }); + mkdirSync(target, { recursive: true }) } writeFileSync(`${target}/index.html`, content) if (url !== '/') { writeFileSync(`${target}.html`, content) } - const stateLookup = ' line.indexOf(stateLookup) > -1).split(stateLookup)[1].slice(0, -2); - const { instances, page } = JSON.parse(decodeURIComponent(state)); + const stateLookup = ' { - const page = pages[path]; - const canonical = `https://${application.project.domain}${path}`; - return `${canonical}${timestamp}${page.changes ? `${page.changes}` : ''}${page.priority ? `${page.priority.toFixed(1)}` : ''}`; - }); - const xml = `${urls.join('')}`; - writeFileSync(`${path()}/sitemap.xml`, xml); + const page = pages[path] + const canonical = `https://${application.project.domain}${path}` + return `${canonical}${timestamp}${ + page.changes ? `${page.changes}` : '' + }${page.priority ? `${page.priority.toFixed(1)}` : ''}` + }) + const xml = `${urls.join( + '', + )}` + writeFileSync(`${path()}/sitemap.xml`, xml) } function filter(src, dest) { @@ -86,27 +95,27 @@ module.exports = async function ssg({ output, cache, environment }) { console.log() if (existsSync(path())) { - removeSync(path()); + removeSync(path()) } mkdirSync(path()) console.log(` ⚙️ /public/`) - copySync(path(`../public`), path()); + copySync(path(`../public`), path()) console.log(` ⚙️ /.${environment}/`) - copySync(path(`../.${environment}`), path(), { filter }); + copySync(path(`../.${environment}`), path(), { filter }) await copyRoute() - await copyRoute(`/nullstack/${application.environment.key}/offline`); - await copyRoute(`/404`); + await copyRoute(`/nullstack/${application.environment.key}/offline`) + await copyRoute(`/404`) await copyBundle(`/manifest.webmanifest`) await copyBundle(`/service-worker.js`) await copyBundle('/robots.txt') await createSitemap() console.log() - console.log('\x1b[36m%s\x1b[0m', ` ✅️ ${projectName} is ready at ${folder}\n`); + console.log('\x1b[36m%s\x1b[0m', ` ✅️ ${projectName} is ready at ${folder}\n`) if (cache) { - console.log('Storing cache...'); + console.log('Storing cache...') } else if (environment === 'production') { - process.exit(); + process.exit() } -} \ No newline at end of file +} diff --git a/builders/ssr.js b/builders/ssr.js index 808e3f48..fd603e5e 100644 --- a/builders/ssr.js +++ b/builders/ssr.js @@ -1,13 +1,14 @@ +/* eslint-disable no-console */ module.exports = async function ssr({ cache }) { - const dir = process.cwd(); - const application = require(`${dir}/.production/server`).default; - const projectName = application.project.name || 'The Nullstack application'; + const dir = process.cwd() + const application = require(`${dir}/.production/server`).default + const projectName = application.project.name || 'The Nullstack application' - console.log('\x1b[36m%s\x1b[0m', `\n ✅️ ${projectName} is ready for production\n`); + console.log('\x1b[36m%s\x1b[0m', `\n ✅️ ${projectName} is ready for production\n`) if (cache) { - console.log('Storing cache...'); + console.log('Storing cache...') } else { - process.exit(); + process.exit() } -} \ No newline at end of file +} diff --git a/client/client.js b/client/client.js index 15f0a670..e6013cab 100644 --- a/client/client.js +++ b/client/client.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import generateTree from '../shared/generateTree' import { loadPlugins } from '../shared/plugins' import context, { generateContext } from './context' @@ -55,7 +56,7 @@ client.processLifecycleQueues = async function processLifecycleQueues() { let shouldScroll = router._hash while (client.initiationQueue.length) { const instance = client.initiationQueue.shift() - instance.initiate && await instance.initiate() + instance.initiate && (await instance.initiate()) instance.initiated = true instance.launch && instance.launch() shouldUpdate = true @@ -72,7 +73,7 @@ client.processLifecycleQueues = async function processLifecycleQueues() { while (client.realHydrationQueue.length) { shouldUpdate = true const instance = client.realHydrationQueue.shift() - instance.hydrate && await instance.hydrate() + instance.hydrate && (await instance.hydrate()) instance.hydrated = true } shouldUpdate && client.update() @@ -86,7 +87,7 @@ client.processLifecycleQueues = async function processLifecycleQueues() { for (const key in client.instances) { const instance = client.instances[key] if (!client.renewalQueue.includes(instance) && !instance.terminated) { - instance.terminate && await instance.terminate() + instance.terminate && (await instance.terminate()) if (instance.persistent) { instance.terminated = true } else { @@ -97,4 +98,4 @@ client.processLifecycleQueues = async function processLifecycleQueues() { router._changed = false } -export default client \ No newline at end of file +export default client diff --git a/client/context.js b/client/context.js index c2a99868..dd50f238 100644 --- a/client/context.js +++ b/client/context.js @@ -1,27 +1,28 @@ -import client from './client'; -import { generateObjectProxy } from './objectProxyHandler'; -import state from './state'; +/* eslint-disable no-undefined */ +import client from './client' +import { generateObjectProxy } from './objectProxyHandler' +import state from './state' -const context = {}; +const context = {} for (const key of Object.keys(state.context)) { - context[key] = generateObjectProxy(key, state.context[key]); + context[key] = generateObjectProxy(key, state.context[key]) } const contextProxyHandler = { set(target, name, value) { - context[name] = generateObjectProxy(name, value); - client.update(); - return true; + context[name] = generateObjectProxy(name, value) + client.update() + return true }, get(target, name) { - if (name === '_isProxy') return true; - return target[name] === undefined ? context[name] : target[name]; - } + if (name === '_isProxy') return true + return target[name] === undefined ? context[name] : target[name] + }, } export function generateContext(temporary) { - return new Proxy(temporary, contextProxyHandler); + return new Proxy(temporary, contextProxyHandler) } -export default context; \ No newline at end of file +export default context diff --git a/client/environment.js b/client/environment.js index 246ce1f6..a80b4089 100644 --- a/client/environment.js +++ b/client/environment.js @@ -1,10 +1,10 @@ -import state from './state'; +import state from './state' const environment = { ...state.environment, client: true, server: false, - event: 'nullstack.environment' -}; + event: 'nullstack.environment', +} -export default environment; \ No newline at end of file +export default environment diff --git a/client/events.js b/client/events.js index 7ac83483..2027fb56 100644 --- a/client/events.js +++ b/client/events.js @@ -1,6 +1,6 @@ -import router from './router' -import { camelize } from '../shared/string'; import noop from '../shared/noop' +import { camelize } from '../shared/string' +import router from './router' export const eventCallbacks = new WeakMap() export const eventSubjects = new WeakMap() @@ -8,9 +8,9 @@ export const eventDebouncer = new WeakMap() function executeEvent(callback, subject, event, data) { if (typeof callback === 'object') { - Object.assign(subject.source, callback); + Object.assign(subject.source, callback) } else { - callback({ ...subject, event, data }); + callback({ ...subject, event, data }) } } @@ -35,28 +35,28 @@ export function generateCallback(selector, name) { router.url = subject.href } } else if (subject.default !== true) { - event.preventDefault(); + event.preventDefault() } debounce(selector, name, subject.debounce, () => { const data = { ...subject.data } for (const attribute in subject) { if (attribute.startsWith('data-')) { - const key = camelize(attribute.slice(5)); - data[key] = subject[attribute]; + const key = camelize(attribute.slice(5)) + data[key] = subject[attribute] } } if (subject?.bind !== undefined) { - const valueName = (subject.type === 'checkbox' || subject.type === 'radio') ? 'checked' : 'value' + const valueName = subject.type === 'checkbox' || subject.type === 'radio' ? 'checked' : 'value' const object = subject.bind.object const property = subject.bind.property if (valueName === 'checked') { - object[property] = event.target[valueName]; + object[property] = event.target[valueName] } else if (object[property] === true || object[property] === false) { - object[property] = event.target[valueName] === 'true'; + object[property] = event.target[valueName] === 'true' } else if (typeof object[property] === 'number') { - object[property] = +event.target[valueName] || 0; + object[property] = +event.target[valueName] || 0 } else { - object[property] = event.target[valueName]; + object[property] = event.target[valueName] } } if (subject[name] === noop) return @@ -69,4 +69,4 @@ export function generateCallback(selector, name) { } }) } -}; \ No newline at end of file +} diff --git a/client/hydrate.js b/client/hydrate.js index e07821ac..49e3fe5b 100644 --- a/client/hydrate.js +++ b/client/hydrate.js @@ -1,30 +1,33 @@ -import { ref } from './ref'; -import { isFalse } from '../shared/nodes'; -import { anchorableElement } from './anchorableNode'; -import client from './client'; +/* eslint-disable no-console */ +import { isFalse } from '../shared/nodes' +import { anchorableElement } from './anchorableNode' +import client from './client' +import { ref } from './ref' let pool = [] function hydrateBody(selector, node) { if (node?.attributes?.html) { - anchorableElement(selector); + anchorableElement(selector) } node.element = selector ref(node.attributes, selector) for (const element of selector.childNodes) { if ((element.tagName === 'TEXTAREA' || element.tagName === 'textarea') && element.childNodes.length === 0) { - element.appendChild(document.createTextNode('')); + element.appendChild(document.createTextNode('')) } else if (element.COMMENT_NODE === 8 && element.textContent === '#') { pool.push(element.remove()) } } if (!node.children) return - const limit = node.children.length; + const limit = node.children.length for (let i = limit - 1; i > -1; i--) { if (node.type !== 'head' && typeof selector?.childNodes?.[i] === 'undefined') { console.error( - `${node.type.toUpperCase()} expected tag ${node.children[i].type.toUpperCase()} to be child at index ${i} but instead found undefined. This error usually happens because of an invalid HTML hierarchy or changes in comparisons after serialization.`, - selector + `${node.type.toUpperCase()} expected tag ${node.children[ + i + ].type.toUpperCase()} to be child at index ${i} but instead found undefined. This error usually happens because of an invalid HTML hierarchy or changes in comparisons after serialization.`, + selector, ) throw new Error('Virtual DOM does not match the DOM.') } @@ -35,7 +38,7 @@ function hydrateBody(selector, node) { function hydrateHead() { for (const node of client.nextHead) { if (isFalse(node)) { - node.element = pool.pop() || document.createComment("") + node.element = pool.pop() || document.createComment('') client.head.append(node.element) } else { node.element = document.getElementById(node.attributes.id) @@ -47,4 +50,4 @@ function hydrateHead() { export default function hydrate(selector, node) { hydrateBody(selector, node) hydrateHead() -} \ No newline at end of file +} diff --git a/client/index.js b/client/index.js index e43bea37..adcdf6a5 100644 --- a/client/index.js +++ b/client/index.js @@ -1,90 +1,90 @@ +import element from '../shared/element' +import fragment from '../shared/fragment' +import generateTree from '../shared/generateTree' +import { loadPlugins, useClientPlugins } from '../shared/plugins' +import client from './client' +import context, { generateContext } from './context' +import environment from './environment' +import hydrate from './hydrate' +import instanceProxyHandler, { instanceProxies } from './instanceProxyHandler' +import invoke from './invoke' +import page from './page' +import params, { updateParams } from './params' +import project from './project' +import render from './render' +import rerender from './rerender' +import router from './router' +import settings from './settings' import state from './state' -import element from '../shared/element'; -import fragment from '../shared/fragment'; -import generateTree from '../shared/generateTree'; -import { loadPlugins, useClientPlugins } from '../shared/plugins'; -import client from './client'; -import context, { generateContext } from './context'; -import environment from './environment'; -import instanceProxyHandler, { instanceProxies } from './instanceProxyHandler'; -import invoke from './invoke'; -import page from './page'; -import params, { updateParams } from './params'; -import project from './project'; -import render from './render'; -import rerender from './rerender'; -import hydrate from './hydrate'; -import router from './router'; -import settings from './settings'; -import worker from './worker'; import windowEvent from './windowEvent' +import worker from './worker' -context.page = page; -context.router = router; -context.settings = settings; -context.worker = worker; -context.params = params; -context.project = project; -context.environment = state.environment; +context.page = page +context.router = router +context.settings = settings +context.worker = worker +context.params = params +context.project = project +context.environment = state.environment -client.memory = state.instances; +client.memory = state.instances -const scope = client; -scope.generateContext = generateContext; -scope.context = context; +const scope = client +scope.generateContext = generateContext +scope.context = context -client.plugins = loadPlugins(scope); +client.plugins = loadPlugins(scope) export default class Nullstack { - static element = element; - static invoke = invoke; - static fragment = fragment; - static use = useClientPlugins; + static element = element + static invoke = invoke + static fragment = fragment + static use = useClientPlugins static context = generateContext({}) static start(Starter) { setTimeout(async () => { window.addEventListener('popstate', () => { - router._popState(); - }); + router._popState() + }) if (client.initializer) { - client.initializer = () => element(Starter); + client.initializer = () => element(Starter) client.update() return this.context } - client.routes = {}; - updateParams(router.url); - client.currentInstance = null; - client.initializer = () => element(Starter); - client.selector = document.getElementById('application'); + client.routes = {} + updateParams(router.url) + client.currentInstance = null + client.initializer = () => element(Starter) + client.selector = document.getElementById('application') if (environment.mode === 'spa') { - scope.plugins = loadPlugins(scope); - worker.online = navigator.onLine; - typeof context.start === 'function' && await context.start(context); - context.environment = environment; - client.virtualDom = await generateTree(client.initializer(), scope); - const body = render(client.virtualDom); - client.selector.replaceWith(body); + scope.plugins = loadPlugins(scope) + worker.online = navigator.onLine + typeof context.start === 'function' && (await context.start(context)) + context.environment = environment + client.virtualDom = await generateTree(client.initializer(), scope) + const body = render(client.virtualDom) + client.selector.replaceWith(body) client.selector = body } else { - client.virtualDom = await generateTree(client.initializer(), scope); + client.virtualDom = await generateTree(client.initializer(), scope) hydrate(client.selector, client.virtualDom) client.currentBody = client.nextBody client.currentHead = client.nextHead client.nextBody = {} client.nextHead = [] - context.environment = environment; - scope.plugins = loadPlugins(scope); - worker.online = navigator.onLine; - typeof context.start === 'function' && await context.start(context); - client.nextVirtualDom = await generateTree(client.initializer(), scope); - rerender(); + context.environment = environment + scope.plugins = loadPlugins(scope) + worker.online = navigator.onLine + typeof context.start === 'function' && (await context.start(context)) + client.nextVirtualDom = await generateTree(client.initializer(), scope) + rerender() } - client.processLifecycleQueues(); - delete state.context; + client.processLifecycleQueues() + delete state.context }, 0) - return this.context; + return this.context } prerendered = false @@ -94,13 +94,13 @@ export default class Nullstack { key = null constructor() { - const proxy = new Proxy(this, instanceProxyHandler); + const proxy = new Proxy(this, instanceProxyHandler) instanceProxies.set(this, proxy) - return proxy; + return proxy } render() { - return false; + return false } } @@ -109,7 +109,7 @@ if (module.hot) { Nullstack.serverHashes ??= {} Nullstack.serverPings = 0 Nullstack.clientPings = 0 - const socket = new WebSocket('ws' + router.base.slice(4) + '/ws'); + const socket = new WebSocket(`ws${router.base.slice(4)}/ws`) socket.onmessage = async function (e) { const data = JSON.parse(e.data) if (data.type === 'NULLSTACK_SERVER_STARTED') { @@ -118,12 +118,12 @@ if (module.hot) { window.location.reload() } } - }; + } Nullstack.updateInstancesPrototypes = function updateInstancesPrototypes(klass, hash, serverHash) { for (const key in context.instances) { const instance = context.instances[key] if (instance.constructor.hash === hash) { - Object.setPrototypeOf(instance, klass.prototype); + Object.setPrototypeOf(instance, klass.prototype) } } if (Nullstack.serverHashes[hash]) { @@ -143,9 +143,9 @@ if (module.hot) { if (client.skipHotReplacement) { window.location.reload() } else { - Nullstack.start(klass); - windowEvent('environment'); + Nullstack.start(klass) + windowEvent('environment') } } module.hot.decline() -} \ No newline at end of file +} diff --git a/client/instanceProxyHandler.js b/client/instanceProxyHandler.js index fdf83cbd..9a9e1d5f 100644 --- a/client/instanceProxyHandler.js +++ b/client/instanceProxyHandler.js @@ -1,12 +1,13 @@ -import client from './client'; -import { generateContext } from './context'; -import { generateObjectProxy } from './objectProxyHandler'; +/* eslint-disable prefer-rest-params */ +import client from './client' +import { generateContext } from './context' +import { generateObjectProxy } from './objectProxyHandler' export const instanceProxies = new WeakMap() const instanceProxyHandler = { get(target, name) { - if (name === '_isProxy') return true; + if (name === '_isProxy') return true if (target.constructor[name]?.name === '_invoke') return target.constructor[name].bind(target.constructor) if (typeof target[name] === 'function' && name !== 'constructor') { const proxy = instanceProxies.get(target) @@ -15,23 +16,23 @@ const instanceProxyHandler = { } const { [name]: named } = { [name]: (args) => { - const context = generateContext({ ...target._attributes, ...args }); - return target[name].call(proxy, context); - } + const context = generateContext({ ...target._attributes, ...args }) + return target[name].call(proxy, context) + }, } - return named; + return named } - return Reflect.get(...arguments); + return Reflect.get(...arguments) }, set(target, name, value) { if (!name.startsWith('_')) { - target[name] = generateObjectProxy(name, value); - client.update(); + target[name] = generateObjectProxy(name, value) + client.update() } else { - target[name] = value; + target[name] = value } - return true; - } + return true + }, } -export default instanceProxyHandler; \ No newline at end of file +export default instanceProxyHandler diff --git a/client/invoke.js b/client/invoke.js index 5c60b046..c5b78d4c 100644 --- a/client/invoke.js +++ b/client/invoke.js @@ -1,20 +1,20 @@ -import deserialize from '../shared/deserialize'; -import prefix from '../shared/prefix'; -import page from './page'; -import worker from './worker'; +import deserialize from '../shared/deserialize' +import prefix from '../shared/prefix' +import page from './page' +import worker from './worker' export default function invoke(name, hash) { return async function _invoke(params = {}) { - let payload; - worker.fetching = true; + let payload + worker.fetching = true if (Object.isFrozen(worker.queues[name])) { - worker.queues[name] = [params]; + worker.queues[name] = [params] } else { - worker.queues[name] = [...worker.queues[name], params]; + worker.queues[name] = [...worker.queues[name], params] } - const finalHash = hash === this.hash ? hash : `${hash}-${this.hash}`; - let url = `${worker.api}/${prefix}/${finalHash}/${name}.json`; - let body = JSON.stringify(params || {}); + const finalHash = hash === this.hash ? hash : `${hash}-${this.hash}` + let url = `${worker.api}/${prefix}/${finalHash}/${name}.json` + const body = JSON.stringify(params || {}) const options = { headers: worker.headers, mode: 'cors', @@ -24,35 +24,35 @@ export default function invoke(name, hash) { referrerPolicy: 'no-referrer', } if (/get[A-Z]([*]*)/.test(name)) { - options.method = 'GET'; - url += `?payload=${encodeURIComponent(body)}`; + options.method = 'GET' + url += `?payload=${encodeURIComponent(body)}` } else { - options.body = body; + options.body = body if (/patch[A-Z]([*]*)/.test(name)) { - options.method = 'PATCH'; + options.method = 'PATCH' } else if (/put[A-Z]([*]*)/.test(name)) { - options.method = 'PUT'; + options.method = 'PUT' } else if (/delete[A-Z]([*]*)/.test(name)) { - options.method = 'DELETE'; + options.method = 'DELETE' } else { - options.method = 'POST'; + options.method = 'POST' } } try { - const response = await fetch(url, options); - page.status = response.status; - const text = await response.text(); - payload = deserialize(text).result; - worker.responsive = true; + const response = await fetch(url, options) + page.status = response.status + const text = await response.text() + payload = deserialize(text).result + worker.responsive = true } catch (e) { - worker.responsive = false; + worker.responsive = false } if (worker.queues[name]?.length === 1) { - delete worker.queues[name]; + delete worker.queues[name] } else { - worker.queues[name] = worker.queues[name].filter((task) => task !== params); + worker.queues[name] = worker.queues[name].filter((task) => task !== params) } - worker.fetching = !!Object.keys(worker.queues).length; - return payload; + worker.fetching = !!Object.keys(worker.queues).length + return payload } -} \ No newline at end of file +} diff --git a/client/objectProxyHandler.js b/client/objectProxyHandler.js index 0c544440..06b27d06 100644 --- a/client/objectProxyHandler.js +++ b/client/objectProxyHandler.js @@ -1,21 +1,22 @@ -import client from './client'; +/* eslint-disable prefer-rest-params */ +import client from './client' const objectProxyHandler = { set(target, name, value) { if (isProxyable(name, value)) { - target[name] = new Proxy(value, this); + target[name] = new Proxy(value, this) } else { - target[name] = value; + target[name] = value } if (!name.startsWith('_')) { - client.update(); + client.update() } - return true; + return true }, get(target, name) { - if (name === '_isProxy') return true; - return Reflect.get(...arguments); - } + if (name === '_isProxy') return true + return Reflect.get(...arguments) + }, } function isProxyable(name, value) { @@ -28,15 +29,14 @@ function isProxyable(name, value) { export function generateObjectProxy(name, value) { if (isProxyable(name, value)) { - if (typeof (value) === 'object') { + if (typeof value === 'object') { for (const key of Object.keys(value)) { - value[key] = generateObjectProxy(key, value[key]); + value[key] = generateObjectProxy(key, value[key]) } } - return new Proxy(value, objectProxyHandler); - } else { - return value; + return new Proxy(value, objectProxyHandler) } + return value } -export default objectProxyHandler; \ No newline at end of file +export default objectProxyHandler diff --git a/client/page.js b/client/page.js index bb5e5417..e9ad4c51 100644 --- a/client/page.js +++ b/client/page.js @@ -1,28 +1,29 @@ -import client from './client'; -import windowEvent from './windowEvent'; -import state from './state'; +/* eslint-disable prefer-rest-params */ +import client from './client' +import state from './state' +import windowEvent from './windowEvent' const page = { ...state.page, - event: 'nullstack.page' + event: 'nullstack.page', } -delete state.page; +delete state.page const pageProxyHandler = { set(target, name, value) { if (name === 'title') { - document.title = value; + document.title = value } - const result = Reflect.set(...arguments); + const result = Reflect.set(...arguments) if (name === 'title') { - windowEvent('page'); + windowEvent('page') } - client.update(); - return result; - } + client.update() + return result + }, } -const proxy = new Proxy(page, pageProxyHandler); +const proxy = new Proxy(page, pageProxyHandler) -export default proxy; \ No newline at end of file +export default proxy diff --git a/client/params.js b/client/params.js index 323b6d6f..783ac415 100644 --- a/client/params.js +++ b/client/params.js @@ -1,38 +1,38 @@ -import router from './router'; -import getQueryStringParams from '../shared/getQueryStringParams'; -import seserializeParam from '../shared/serializeParam'; -import serializeSearch from '../shared/serializeSearch'; -import segments, { resetSegments } from './segments'; -import state from './state'; +import getQueryStringParams from '../shared/getQueryStringParams' +import seserializeParam from '../shared/serializeParam' +import serializeSearch from '../shared/serializeSearch' +import router from './router' +import segments, { resetSegments } from './segments' +import state from './state' const paramsProxyHandler = { set(target, name, value) { - const serializedValue = seserializeParam(value); - target[name] = serializedValue; - const search = serializeSearch(target); - router.url = router.path + (search ? '?' : '') + search; - return true; + const serializedValue = seserializeParam(value) + target[name] = serializedValue + const search = serializeSearch(target) + router.url = router.path + (search ? '?' : '') + search + return true }, get(target, name) { - if (target[name] === false) return false; - if (segments[name] === false) return false; - return target[name] || segments[name] || ''; - } + if (target[name] === false) return false + if (segments[name] === false) return false + return target[name] || segments[name] || '' + }, } -const params = { ...state.params }; +const params = { ...state.params } -delete state.params; +delete state.params -const proxy = new Proxy(params, paramsProxyHandler); +const proxy = new Proxy(params, paramsProxyHandler) export function updateParams(query) { - resetSegments(); - const delta = getQueryStringParams(query); + resetSegments() + const delta = getQueryStringParams(query) for (const key of Object.keys({ ...delta, ...params })) { - params[key] = delta[key]; + params[key] = delta[key] } - return proxy; + return proxy } -export default proxy; \ No newline at end of file +export default proxy diff --git a/client/project.js b/client/project.js index f4e426ed..f3ad5e87 100644 --- a/client/project.js +++ b/client/project.js @@ -1,9 +1,9 @@ import state from './state' -const project = { ...state.project }; +const project = { ...state.project } -delete state.project; +delete state.project -Object.freeze(project); +Object.freeze(project) -export default project; \ No newline at end of file +export default project diff --git a/client/ref.js b/client/ref.js index 5cb9775f..d80a332d 100644 --- a/client/ref.js +++ b/client/ref.js @@ -24,4 +24,4 @@ export function reref(attributes, element) { const map = refMap.get(attributes.ref.object) if (map?.[attributes.ref.property]) return setup(attributes, element) -} \ No newline at end of file +} diff --git a/client/render.js b/client/render.js index 6ca7e134..a62fafbd 100644 --- a/client/render.js +++ b/client/render.js @@ -1,57 +1,58 @@ -import { isFalse, isText } from '../shared/nodes'; -import { anchorableElement } from './anchorableNode'; +/* eslint-disable no-undefined */ +/* eslint-disable no-continue */ +import generateTruthyString from '../shared/generateTruthyString' +import { isFalse, isText } from '../shared/nodes' +import { anchorableElement } from './anchorableNode' import { eventCallbacks, eventSubjects, generateCallback } from './events' import { ref } from './ref' -import generateTruthyString from '../shared/generateTruthyString'; export default function render(node, options) { - if (isFalse(node) || node.type === 'head') { - node.element = document.createComment(""); + node.element = document.createComment('') return node.element } if (isText(node)) { - node.element = document.createTextNode(node.text); + node.element = document.createTextNode(node.text) return node.element } - const svg = (options && options.svg) || node.type === 'svg'; + const svg = (options && options.svg) || node.type === 'svg' if (svg) { - node.element = document.createElementNS("http://www.w3.org/2000/svg", node.type); + node.element = document.createElementNS('http://www.w3.org/2000/svg', node.type) } else { - node.element = document.createElement(node.type); + node.element = document.createElement(node.type) } ref(node.attributes, node.element) - for (let name in node.attributes) { + for (const name in node.attributes) { if (name === 'debounce') continue if (name === 'html') { - node.element.innerHTML = node.attributes[name]; - node.head || anchorableElement(node.element); + node.element.innerHTML = node.attributes[name] + node.head || anchorableElement(node.element) } else if (name.startsWith('on')) { if (node.attributes[name] !== undefined) { - const eventName = name.substring(2); + const eventName = name.substring(2) const callback = generateCallback(node.element, name) - node.element.addEventListener(eventName, callback); + node.element.addEventListener(eventName, callback) eventCallbacks.set(node.element, callback) eventSubjects.set(node.element, node.attributes) } } else { - let nodeValue; + let nodeValue if ((name === 'class' || name === 'style') && Array.isArray(node.attributes[name])) { nodeValue = generateTruthyString(node.attributes[name]) } else { nodeValue = node.attributes[name] } - const type = typeof nodeValue; + const type = typeof nodeValue if (type !== 'object' && type !== 'function') { if (name != 'value' && nodeValue === true) { - node.element.setAttribute(name, ''); + node.element.setAttribute(name, '') } else if (name === 'value' || (nodeValue !== false && nodeValue !== null && nodeValue !== undefined)) { - node.element.setAttribute(name, nodeValue); + node.element.setAttribute(name, nodeValue) } } } @@ -59,15 +60,14 @@ export default function render(node, options) { if (!node.attributes.html) { for (let i = 0; i < node.children.length; i++) { - const child = render(node.children[i], { svg }); - node.element.appendChild(child); + const child = render(node.children[i], { svg }) + node.element.appendChild(child) } if (node.type === 'select') { - node.element.value = node.attributes.value; + node.element.value = node.attributes.value } } - return node.element; - -} \ No newline at end of file + return node.element +} diff --git a/client/rerender.js b/client/rerender.js index 870c27a8..407c889b 100644 --- a/client/rerender.js +++ b/client/rerender.js @@ -1,63 +1,65 @@ -import { isUndefined, isFalse, isText } from '../shared/nodes'; -import { anchorableElement } from './anchorableNode'; -import client from './client'; -import render from './render'; -import { generateCallback, eventCallbacks, eventSubjects } from './events' -import generateTruthyString from '../shared/generateTruthyString'; -import { reref } from './ref'; +/* eslint-disable no-undefined */ +/* eslint-disable no-continue */ +import generateTruthyString from '../shared/generateTruthyString' +import { isFalse, isText, isUndefined } from '../shared/nodes' +import { anchorableElement } from './anchorableNode' +import client from './client' +import { eventCallbacks, eventSubjects, generateCallback } from './events' +import { reref } from './ref' +import render from './render' function updateAttributes(selector, currentAttributes, nextAttributes) { - const attributeNames = Object.keys({ ...currentAttributes, ...nextAttributes }); + const attributeNames = Object.keys({ ...currentAttributes, ...nextAttributes }) for (const name of attributeNames) { if (name === 'debounce') continue if (name === 'ref' && nextAttributes?.ref?.property) { reref(nextAttributes, selector) } else if (name === 'html') { if (nextAttributes[name] !== currentAttributes[name]) { - selector.innerHTML = nextAttributes[name]; - anchorableElement(selector); + selector.innerHTML = nextAttributes[name] + anchorableElement(selector) } } else if (name === 'checked' || name === 'value') { if (nextAttributes[name] !== currentAttributes[name] && nextAttributes[name] !== selector[name]) { - selector[name] = nextAttributes[name]; + selector[name] = nextAttributes[name] } } else if (name.startsWith('on')) { - const eventName = name.substring(2); + const eventName = name.substring(2) if (eventCallbacks.has(selector) && !nextAttributes[name]) { - selector.removeEventListener(eventName, eventCallbacks.get(selector)); + selector.removeEventListener(eventName, eventCallbacks.get(selector)) } if (nextAttributes[name]) { if (!eventCallbacks.has(selector)) { const callback = generateCallback(selector, name) - selector.addEventListener(eventName, callback); + selector.addEventListener(eventName, callback) eventCallbacks.set(selector, callback) } eventSubjects.set(selector, nextAttributes) } } else { - let currentValue; + let currentValue if ((name === 'class' || name === 'style') && Array.isArray(currentAttributes[name])) { currentValue = generateTruthyString(currentAttributes[name]) } else { currentValue = currentAttributes[name] } - let nextValue; + let nextValue if ((name === 'class' || name === 'style') && Array.isArray(nextAttributes[name])) { nextValue = generateTruthyString(nextAttributes[name]) } else { nextValue = nextAttributes[name] } - const type = typeof nextValue; + const type = typeof nextValue if (type !== 'object' && type !== 'function') { if (currentValue !== undefined && nextValue === undefined) { - selector.removeAttribute(name); + selector.removeAttribute(name) } else if (currentValue !== nextValue) { - if (name != 'value' && nextValue === false || nextValue === null || nextValue === undefined) { - selector.removeAttribute(name); - } else if (name != 'value' && nextValue === true) { - selector.setAttribute(name, ''); + if ((name !== 'value' && nextValue === false) || nextValue === null || nextValue === undefined) { + selector.removeAttribute(name) + } else if (name !== 'value' && nextValue === true) { + selector.setAttribute(name, '') } else { - selector.setAttribute(name, nextValue); + selector.setAttribute(name, nextValue) } } } @@ -67,7 +69,7 @@ function updateAttributes(selector, currentAttributes, nextAttributes) { function updateHeadChild(current, next) { if (isUndefined(current) && !isUndefined(next)) { - const nextSelector = render(next); + const nextSelector = render(next) client.head.append(nextSelector) return } @@ -80,7 +82,7 @@ function updateHeadChild(current, next) { return } if (current.type !== next.type) { - const nextSelector = render(next); + const nextSelector = render(next) current.element.replaceWith(nextSelector) return } @@ -88,24 +90,23 @@ function updateHeadChild(current, next) { } function updateHeadChildren(currentChildren, nextChildren) { - const limit = Math.max(currentChildren.length, nextChildren.length); + const limit = Math.max(currentChildren.length, nextChildren.length) for (let i = 0; i < limit; i++) { updateHeadChild(currentChildren[i], nextChildren[i]) } } function _rerender(current, next) { - const selector = current.element next.element = current.element if (isFalse(current) && isFalse(next)) { - return; + return } if (current.type !== next.type) { - const nextSelector = render(next); - selector.replaceWith(nextSelector); + const nextSelector = render(next) + selector.replaceWith(nextSelector) return } @@ -118,37 +119,36 @@ function _rerender(current, next) { if (isText(current) && isText(next)) { if (current.text !== next.text) { - selector.textContent = next.text; + selector.textContent = next.text } - return; + return } if (!next.attributes.html) { - const limit = Math.max(current.children.length, next.children.length); + const limit = Math.max(current.children.length, next.children.length) if (next.children.length > current.children.length) { for (let i = 0; i < current.children.length; i++) { - _rerender(current.children[i], next.children[i]); + _rerender(current.children[i], next.children[i]) } for (let i = current.children.length; i < next.children.length; i++) { - const nextSelector = render(next.children[i]); - selector.appendChild(nextSelector); + const nextSelector = render(next.children[i]) + selector.appendChild(nextSelector) } } else if (current.children.length > next.children.length) { for (let i = 0; i < next.children.length; i++) { - _rerender(current.children[i], next.children[i]); + _rerender(current.children[i], next.children[i]) } for (let i = current.children.length - 1; i >= next.children.length; i--) { selector.childNodes[i].remove() } } else { for (let i = limit - 1; i > -1; i--) { - _rerender(current.children[i], next.children[i]); + _rerender(current.children[i], next.children[i]) } } } updateAttributes(selector, current.attributes, next.attributes) - } export default function rerender() { diff --git a/client/router.js b/client/router.js index 28e2415d..cbf1e536 100644 --- a/client/router.js +++ b/client/router.js @@ -1,91 +1,91 @@ -import extractLocation from '../shared/extractLocation'; -import client from './client'; -import environment from './environment'; -import page from './page'; -import { updateParams } from './params'; -import segments from './segments'; -import windowEvent from './windowEvent'; -import worker from './worker'; +import extractLocation from '../shared/extractLocation' +import client from './client' +import environment from './environment' +import page from './page' +import { updateParams } from './params' +import segments from './segments' +import windowEvent from './windowEvent' +import worker from './worker' -let redirectTimer = null; +let redirectTimer = null class Router { event = 'nullstack.router'; - previous = null; - _changed = false; - _segments = segments; + previous = null + _changed = false + _segments = segments constructor() { - const { hash, url } = extractLocation(window.location.pathname + window.location.search); - this._url = url; - this._hash = hash; + const { hash, url } = extractLocation(window.location.pathname + window.location.search) + this._url = url + this._hash = hash } async _popState() { - const { urlWithHash } = extractLocation(window.location.pathname + window.location.search); - await this._update(urlWithHash, false); + const { urlWithHash } = extractLocation(window.location.pathname + window.location.search) + await this._update(urlWithHash, false) } async _update(target, push) { - const { url, path, hash, urlWithHash } = extractLocation(target); + const { url, path, hash, urlWithHash } = extractLocation(target) if (url === this._url && this._hash === hash) return - this.previous = this.url; - clearTimeout(redirectTimer); + this.previous = this.url + clearTimeout(redirectTimer) redirectTimer = setTimeout(async () => { - page.status = 200; + page.status = 200 if (environment.mode === 'ssg') { - worker.fetching = true; - const api = '/index.json'; - const endpoint = path === '/' ? api : path + api; + worker.fetching = true + const api = '/index.json' + const endpoint = path === '/' ? api : path + api try { - const response = await fetch(endpoint); - const payload = await response.json(url); - client.memory = payload.instances; + const response = await fetch(endpoint) + const payload = await response.json(url) + client.memory = payload.instances for (const key in payload.page) { - page[key] = payload.page[key]; + page[key] = payload.page[key] } - worker.responsive = true; + worker.responsive = true } catch (error) { - worker.responsive = false; + worker.responsive = false } - worker.fetching = false; + worker.fetching = false } if (push) { - history.pushState({}, document.title, urlWithHash); + history.pushState({}, document.title, urlWithHash) } - this._url = url; - this._hash = hash; - this._changed = true; - updateParams(url); - client.update(); - windowEvent('router'); - }, 0); + this._url = url + this._hash = hash + this._changed = true + updateParams(url) + client.update() + windowEvent('router') + }, 0) } async _redirect(target) { if (/^(\w+:|\/\/)([^.]+.)/.test(target)) { - return window.location.href = target; + return (window.location.href = target) } - const absoluteUrl = new URL(target, document.baseURI); - await this._update(absoluteUrl.pathname + absoluteUrl.search + absoluteUrl.hash, true); + const absoluteUrl = new URL(target, document.baseURI) + await this._update(absoluteUrl.pathname + absoluteUrl.search + absoluteUrl.hash, true) window.scroll(0, 0) } get url() { - return this._url; + return this._url } set url(target) { - this._redirect(target); + this._redirect(target) } get path() { - return extractLocation(this._url).path; + return extractLocation(this._url).path } set path(target) { - this._redirect(target + window.location.search); + this._redirect(target + window.location.search) } get base() { @@ -96,6 +96,6 @@ class Router { } -const router = new Router(); +const router = new Router() -export default router; \ No newline at end of file +export default router diff --git a/client/segments.js b/client/segments.js index 21ee36ce..7aabab6e 100644 --- a/client/segments.js +++ b/client/segments.js @@ -1,8 +1,8 @@ -const segments = {}; -export default segments; +const segments = {} +export default segments export function resetSegments() { - for(const key in segments) { - delete segments[key]; + for (const key in segments) { + delete segments[key] } -} \ No newline at end of file +} diff --git a/client/settings.js b/client/settings.js index 90d5daf1..7f0adedd 100644 --- a/client/settings.js +++ b/client/settings.js @@ -1,8 +1,8 @@ -import state from './state'; +import state from './state' -const settings = { ...state.settings }; -delete state.settings; +const settings = { ...state.settings } +delete state.settings -Object.freeze(settings); +Object.freeze(settings) -export default settings; \ No newline at end of file +export default settings diff --git a/client/state.js b/client/state.js index 5d2cf3bd..5ba1af33 100644 --- a/client/state.js +++ b/client/state.js @@ -1,5 +1,5 @@ -import deserialize from '../shared/deserialize'; +import deserialize from '../shared/deserialize' -const state = deserialize(decodeURIComponent(document.querySelector(`[name=nullstack]`).content)); +const state = deserialize(decodeURIComponent(document.querySelector(`[name=nullstack]`).content)) -export default state; \ No newline at end of file +export default state diff --git a/client/windowEvent.js b/client/windowEvent.js index 601e51bf..54a8b280 100644 --- a/client/windowEvent.js +++ b/client/windowEvent.js @@ -1,9 +1,9 @@ -let timer = null; +const timer = null export default function windowEvent(name) { - clearTimeout(timer); + clearTimeout(timer) setTimeout(() => { - const event = new Event('nullstack.' + name); - window.dispatchEvent(event); - }, 0); -} \ No newline at end of file + const event = new Event(`nullstack.${name}`) + window.dispatchEvent(event) + }, 0) +} diff --git a/client/worker.js b/client/worker.js index 492a94a6..11135b5c 100644 --- a/client/worker.js +++ b/client/worker.js @@ -1,71 +1,69 @@ -import client from './client'; -import environment from './environment'; -import router from './router'; +/* eslint-disable no-console */ +import client from './client' +import environment from './environment' +import router from './router' import state from './state' -const worker = { ...state.worker }; -delete state.worker; +const worker = { ...state.worker } +delete state.worker -const emptyQueue = Object.freeze([]); +const emptyQueue = Object.freeze([]) const queuesProxyHandler = { set(target, name, value) { - target[name] = value; - client.update(); - return true; + target[name] = value + client.update() + return true }, get(target, name) { - return target[name] || emptyQueue; - } + return target[name] || emptyQueue + }, } -worker.queues = new Proxy({}, queuesProxyHandler); +worker.queues = new Proxy({}, queuesProxyHandler) const workerProxyHandler = { set(target, name, value) { if (target[name] !== value) { - target[name] = value; - client.update(); + target[name] = value + client.update() } - return true; - } + return true + }, } -const proxy = new Proxy(worker, workerProxyHandler); +const proxy = new Proxy(worker, workerProxyHandler) -if (worker.enabled) { - - window.addEventListener('beforeinstallprompt', function (event) { - event.preventDefault(); - proxy.installation = event; - }); - - async function register() { - if ('serviceWorker' in navigator) { - const request = `/service-worker.js`; - try { - proxy.registration = await navigator.serviceWorker.register(request, { scope: '/' }); - } catch (error) { - console.log(error); - }; +async function register() { + if ('serviceWorker' in navigator) { + const request = `/service-worker.js` + try { + proxy.registration = await navigator.serviceWorker.register(request, { scope: '/' }) + } catch (error) { + console.log(error) } - }; - - register(); + } +} +if (worker.enabled) { + window.addEventListener('beforeinstallprompt', function (event) { + event.preventDefault() + proxy.installation = event + }) + register() } window.addEventListener('online', () => { - proxy.online = true; + proxy.online = true if (environment.mode === 'ssg') { - router._update(router.url); + router._update(router.url) } else { - proxy.responsive = true; + proxy.responsive = true } -}); +}) window.addEventListener('offline', () => { - proxy.online = false; -}); + proxy.online = false +}) -export default proxy; \ No newline at end of file +export default proxy diff --git a/loaders/add-source-to-node.js b/loaders/add-source-to-node.js index 48d89ce5..a57f20c8 100644 --- a/loaders/add-source-to-node.js +++ b/loaders/add-source-to-node.js @@ -1,10 +1,12 @@ module.exports = function (source) { - let tags = source.split('<'); - return tags.map((tag) => { - match = tag.match(/\ on([a-z]*?)\=\{(.*?)\}/); - if (match && tag.indexOf('source={') === -1) { - return tag.substring(0, match.index) + ' source={this}' + tag.substring(match.index); - } - return tag; - }).join('<'); -} \ No newline at end of file + const tags = source.split('<') + return tags + .map((tag) => { + const match = tag.match(/\ on([a-z]*?)\=\{(.*?)\}/) + if (match && tag.indexOf('source={') === -1) { + return `${tag.substring(0, match.index)} source={this}${tag.substring(match.index)}` + } + return tag + }) + .join('<') +} diff --git a/loaders/ignore-import.js b/loaders/ignore-import.js index 27ca5da3..a980b3a1 100644 --- a/loaders/ignore-import.js +++ b/loaders/ignore-import.js @@ -1,10 +1,10 @@ -'use strict'; +'use strict' /* Copyright (c) 2016 Cherry Ng MIT Licensed https://npmjs.com/package/ignore-loader */ -module.exports = function() { - this.cacheable && this.cacheable(); - return ''; -} \ No newline at end of file +module.exports = function () { + this.cacheable && this.cacheable() + return '' +} diff --git a/loaders/inject-hmr.js b/loaders/inject-hmr.js index 28fb5af8..24dd6ae2 100644 --- a/loaders/inject-hmr.js +++ b/loaders/inject-hmr.js @@ -1,11 +1,11 @@ -const parse = require('@babel/parser').parse; -const traverse = require("@babel/traverse").default; +const parse = require('@babel/parser').parse +const traverse = require('@babel/traverse').default module.exports = function (source) { const ast = parse(source, { sourceType: 'module', - plugins: ['classProperties', 'jsx'] - }); + plugins: ['classProperties', 'jsx'], + }) let klassName let klassPath traverse(ast, { @@ -13,18 +13,18 @@ module.exports = function (source) { if (path.node.property.name === 'start' && path.node.object && path.node.object.name === 'Nullstack') { klassName = path.parent.arguments[0].name } - } - }); + }, + }) if (!klassName) return source traverse(ast, { ImportDeclaration(path) { if (path.node.specifiers[0].local.name === klassName) { klassPath = path.node.source.extra.rawValue } - } - }); + }, + }) - return source + ` + return `${source} if (module.hot) { if (Nullstack.needsClientReload) { window.location.reload() @@ -36,4 +36,4 @@ module.exports = function (source) { }) } ` -} \ No newline at end of file +} diff --git a/loaders/inject-nullstack.js b/loaders/inject-nullstack.js index 396aca8c..e58d65c0 100644 --- a/loaders/inject-nullstack.js +++ b/loaders/inject-nullstack.js @@ -1,21 +1,21 @@ -const parse = require('@babel/parser').parse; -const traverse = require("@babel/traverse").default; +const parse = require('@babel/parser').parse +const traverse = require('@babel/traverse').default -module.exports = function(source) { +module.exports = function (source) { const ast = parse(source, { sourceType: 'module', - plugins: ['classProperties', 'jsx'] - }); - let shouldImport = true; + plugins: ['classProperties', 'jsx'], + }) + let shouldImport = true traverse(ast, { ImportDeclaration(path) { - if(path.node.source.value === 'nullstack') { - shouldImport = false; + if (path.node.source.value === 'nullstack') { + shouldImport = false } - } - }); - if(shouldImport) { - source = `import Nullstack from 'nullstack'\n${source}`; + }, + }) + if (shouldImport) { + source = `import Nullstack from 'nullstack'\n${source}` } - return source; -} \ No newline at end of file + return source +} diff --git a/loaders/register-inner-components.js b/loaders/register-inner-components.js index fa5f32c2..e3153f5e 100644 --- a/loaders/register-inner-components.js +++ b/loaders/register-inner-components.js @@ -1,47 +1,53 @@ -const parse = require('@babel/parser').parse; -const traverse = require("@babel/traverse").default; +const parse = require('@babel/parser').parse +const traverse = require('@babel/traverse').default + +function identify(path, positions, injections, subpath) { + if (/^[A-Z]/.test(subpath.node.name)) { + if (!path.scope.hasBinding(subpath.node.name)) { + const start = path.node.body.body[0].start + if (!positions.includes(start)) { + positions.push(start) + } + if (!injections[start]) { + injections[start] = [] + } + if (!injections[start].includes(subpath.node.name)) { + injections[start].push(subpath.node.name) + } + } + } +} module.exports = function (source) { - const injections = {}; - const positions = []; + const injections = {} + const positions = [] const ast = parse(source, { sourceType: 'module', - plugins: ['classProperties', 'jsx', 'typescript'] - }); + plugins: ['classProperties', 'jsx', 'typescript'], + }) traverse(ast, { ClassMethod(path) { if (path.node.key.name.startsWith('render')) { - function identify(subpath) { - if (/^[A-Z]/.test(subpath.node.name)) { - if (!path.scope.hasBinding(subpath.node.name)) { - const start = path.node.body.body[0].start; - if (!positions.includes(start)) { - positions.push(start); - } - if (!injections[start]) { - injections[start] = []; - } - if (!injections[start].includes(subpath.node.name)) { - injections[start].push(subpath.node.name); - } - } - } - } - traverse(path.node, { - JSXIdentifier: identify, - Identifier: identify, - }, path.scope, path); + traverse( + path.node, + { + JSXIdentifier: (subpath) => identify(path, positions, injections, subpath), + Identifier: (subpath) => identify(path, positions, injections, subpath), + }, + path.scope, + path, + ) } - } - }); - positions.reverse(); - positions.push(0); - let outputs = []; - let last; + }, + }) + positions.reverse() + positions.push(0) + const outputs = [] + let last for (const position of positions) { - let code = source.slice(position, last); - last = position; - outputs.push(code); + const code = source.slice(position, last) + last = position + outputs.push(code) if (position) { for (const injection of injections[position]) { if (injection) { @@ -50,5 +56,5 @@ module.exports = function (source) { } } } - return outputs.reverse().join(''); -} \ No newline at end of file + return outputs.reverse().join('') +} diff --git a/loaders/register-static-from-server.js b/loaders/register-static-from-server.js index c299c3c5..36b629e1 100644 --- a/loaders/register-static-from-server.js +++ b/loaders/register-static-from-server.js @@ -1,34 +1,34 @@ -const crypto = require('crypto'); -const parse = require('@babel/parser').parse; -const traverse = require("@babel/traverse").default; +const parse = require('@babel/parser').parse +const traverse = require('@babel/traverse').default +const crypto = require('crypto') module.exports = function (source) { - let hasClass = false; - const legacyHash = crypto.createHash('md5').update(source).digest("hex"); + let hasClass = false + const legacyHash = crypto.createHash('md5').update(source).digest('hex') const id = this.resourcePath.replace(this.rootContext, '') - const hash = crypto.createHash('md5').update(id).digest("hex"); - let klassName; - let klassEnd; - const methodNames = []; + const hash = crypto.createHash('md5').update(id).digest('hex') + let klassName + let klassEnd + const methodNames = [] const ast = parse(source, { sourceType: 'module', - plugins: ['classProperties', 'jsx'] - }); + plugins: ['classProperties', 'jsx'], + }) traverse(ast, { ClassDeclaration(path) { - hasClass = true; - klassEnd = path.node.end - 1; - klassName = path.node.id.name; + hasClass = true + klassEnd = path.node.end - 1 + klassName = path.node.id.name }, ClassMethod(path) { if (path.node.static && path.node.async && !path.node.key.name.startsWith('_')) { - methodNames.push(path.node.key.name); + methodNames.push(path.node.key.name) } - } - }); - if (!hasClass) return source; - let output = source.substring(0, klassEnd); - output += source.substring(klassEnd); + }, + }) + if (!hasClass) return source + let output = source.substring(0, klassEnd) + output += source.substring(klassEnd) for (const methodName of methodNames) { output += `\nNullstack.registry["${hash}.${methodName}"] = ${klassName}.${methodName};` output += `\nNullstack.registry["${legacyHash}.${methodName}"] = ${klassName}.${methodName};` @@ -37,5 +37,5 @@ module.exports = function (source) { output += `\nNullstack.registry["${legacyHash}"] = ${klassName};` output += `\n${klassName}.hash = "${hash}";` output += `\n${klassName}.bindStaticFunctions(${klassName});` - return output; -} \ No newline at end of file + return output +} diff --git a/loaders/remove-import-from-client.js b/loaders/remove-import-from-client.js index d9e2e3ab..80d2d211 100644 --- a/loaders/remove-import-from-client.js +++ b/loaders/remove-import-from-client.js @@ -1,32 +1,32 @@ -const parse = require('@babel/parser').parse; -const traverse = require("@babel/traverse").default; +const parse = require('@babel/parser').parse +const traverse = require('@babel/traverse').default const parentTypes = ['ImportDefaultSpecifier', 'ImportSpecifier', 'ImportNamespaceSpecifier'] module.exports = function (source) { const ast = parse(source, { sourceType: 'module', - plugins: ['classProperties', 'jsx'] - }); - const imports = {}; + plugins: ['classProperties', 'jsx'], + }) + const imports = {} function findImports(path) { if (path.node.local.name !== 'Nullstack') { - const parent = path.findParent((path) => path.isImportDeclaration()); - const start = parent.node.loc.start.line; - const end = parent.node.loc.end.line; - const lines = new Array(end - start + 1).fill().map((d, i) => i + start); - const key = lines.join('.'); - imports[path.node.local.name] = { lines, key }; + const parent = path.findParent((p) => p.isImportDeclaration()) + const start = parent.node.loc.start.line + const end = parent.node.loc.end.line + const lines = new Array(end - start + 1).fill().map((d, i) => i + start) + const key = lines.join('.') + imports[path.node.local.name] = { lines, key } } } function findIdentifiers(path) { if (parentTypes.indexOf(path.parent.type) === -1) { - const target = imports[path.node.name]; + const target = imports[path.node.name] if (target) { for (const name in imports) { if (imports[name].key === target.key) { if (path.parent.type !== 'MemberExpression' || path.parent.object?.type !== 'ThisExpression') { - delete imports[name]; + delete imports[name] } } } @@ -36,12 +36,17 @@ module.exports = function (source) { traverse(ast, { ImportSpecifier: findImports, ImportDefaultSpecifier: findImports, - ImportNamespaceSpecifier: findImports - }); + ImportNamespaceSpecifier: findImports, + }) traverse(ast, { Identifier: findIdentifiers, - JSXIdentifier: findIdentifiers - }); - const lines = Object.keys(imports).map((name) => imports[name].lines).flat(); - return source.split(`\n`).filter((line, index) => !lines.includes(index + 1)).join(`\n`); -} \ No newline at end of file + JSXIdentifier: findIdentifiers, + }) + const lines = Object.keys(imports) + .map((name) => imports[name].lines) + .flat() + return source + .split(`\n`) + .filter((line, index) => !lines.includes(index + 1)) + .join(`\n`) +} diff --git a/loaders/remove-static-from-client.js b/loaders/remove-static-from-client.js index 9a310dc2..3a999d4f 100644 --- a/loaders/remove-static-from-client.js +++ b/loaders/remove-static-from-client.js @@ -1,65 +1,65 @@ -const crypto = require('crypto'); -const parse = require('@babel/parser').parse; -const traverse = require("@babel/traverse").default; +const parse = require('@babel/parser').parse +const traverse = require('@babel/traverse').default +const crypto = require('crypto') module.exports = function removeStaticFromClient(source) { const id = this.resourcePath.replace(this.rootContext, '') - const hash = crypto.createHash('md5').update(id).digest("hex"); + const hash = crypto.createHash('md5').update(id).digest('hex') let serverSource = '' - let hashPosition; - let klassName; - const injections = {}; - const positions = []; + let hashPosition + let klassName + const injections = {} + const positions = [] const ast = parse(source, { sourceType: 'module', - plugins: ['classProperties', 'jsx'] - }); + plugins: ['classProperties', 'jsx'], + }) traverse(ast, { ClassDeclaration(path) { - klassName = path.node.id.name; + klassName = path.node.id.name }, ClassBody(path) { - const start = path.node.body[0].start; - hashPosition = start; - positions.push(start); + const start = path.node.body[0].start + hashPosition = start + positions.push(start) }, ClassMethod(path) { if (path.node.static && path.node.async) { - injections[path.node.start] = { end: path.node.end, name: path.node.key.name }; + injections[path.node.start] = { end: path.node.end, name: path.node.key.name } serverSource += source.slice(path.node.start, path.node.end) if (!positions.includes(path.node.start)) { - positions.push(path.node.start); + positions.push(path.node.start) } } - } - }); - positions.reverse(); - positions.push(0); - let outputs = []; - let last; + }, + }) + positions.reverse() + positions.push(0) + const outputs = [] + let last for (const position of positions) { - let code = source.slice(position, last); - last = position; - const injection = injections[position]; + let code = source.slice(position, last) + last = position + const injection = injections[position] if (position && injection) { - const location = injection.end - position; + const location = injection.end - position if (injection.name.startsWith('_')) { - code = code.substring(location).trimStart(); + code = code.substring(location).trimStart() } else { - code = `static ${injection.name} = Nullstack.invoke('${injection.name}', '${hash}');` + code.substring(location); + code = `static ${injection.name} = Nullstack.invoke('${injection.name}', '${hash}');${code.substring(location)}` } - outputs.push(code); + outputs.push(code) } else { - outputs.push(code); + outputs.push(code) } if (position === hashPosition) { - outputs.push(`static hash = '${hash}';\n\n `); + outputs.push(`static hash = '${hash}';\n\n `) } } let newSource = outputs.reverse().join('') if (klassName) { - const serverHash = crypto.createHash('md5').update(serverSource).digest("hex"); - newSource += `\nif (module.hot) { module.hot.accept(); Nullstack.updateInstancesPrototypes(${klassName}, ${klassName}.hash, '${serverHash}') }`; + const serverHash = crypto.createHash('md5').update(serverSource).digest('hex') + newSource += `\nif (module.hot) { module.hot.accept(); Nullstack.updateInstancesPrototypes(${klassName}, ${klassName}.hash, '${serverHash}') }` } return newSource -} \ No newline at end of file +} diff --git a/loaders/string-replace.js b/loaders/string-replace.js index 2600c201..3ac18f6a 100644 --- a/loaders/string-replace.js +++ b/loaders/string-replace.js @@ -3,18 +3,18 @@ Copyright (c) 2015 Valentyn Barmashyn MIT Licensed Original: https://npmjs.com/package/string-replace-loader */ -module.exports = function(source, map) { - this.cacheable(); +module.exports = function (source, map) { + this.cacheable() - const optionsArray = this.getOptions().multiple; - let newSource = source; + const optionsArray = this.getOptions().multiple + let newSource = source for (const options of optionsArray) { newSource = newSource.replace( new RegExp(options.search, options.search.flags || options.flags || ''), - options.replace - ); + options.replace, + ) } - this.callback(null, newSource, map); -} \ No newline at end of file + this.callback(null, newSource, map) +} diff --git a/loaders/transform-node-ref.js b/loaders/transform-node-ref.js index 8be96e23..6b3a813a 100644 --- a/loaders/transform-node-ref.js +++ b/loaders/transform-node-ref.js @@ -1,13 +1,13 @@ -const parse = require('@babel/parser').parse; -const traverse = require("@babel/traverse").default; +const parse = require('@babel/parser').parse +const traverse = require('@babel/traverse').default const attributes = ['ref', 'bind'] module.exports = function removeStaticFromClient(source) { const ast = parse(source, { sourceType: 'module', - plugins: ['classProperties', 'jsx', 'typescript'] - }); + plugins: ['classProperties', 'jsx', 'typescript'], + }) const refs = [] traverse(ast, { JSXAttribute(path) { @@ -22,16 +22,16 @@ module.exports = function removeStaticFromClient(source) { if (property.type === 'Identifier' && !expression.computed) { refProperty = `'${refProperty}'` } - replacement = `${attribute}={{object: ${refObject}, property: ${refProperty}}}` + const replacement = `${attribute}={{object: ${refObject}, property: ${refProperty}}}` refs.push({ start: path.node.start, end: path.node.end, - replacement + replacement, }) } } - } - }); + }, + }) if (refs.length === 0) return source const sources = [] for (let i = 0; i <= refs.length; i++) { @@ -46,4 +46,4 @@ module.exports = function removeStaticFromClient(source) { } } return sources.join('') -} \ No newline at end of file +} diff --git a/logo.tsx b/logo.tsx index 3097d049..4402ff99 100644 --- a/logo.tsx +++ b/logo.tsx @@ -6,8 +6,8 @@ type NullstackLogoProps = { } export default function Logo({ light, height, monotone }: NullstackLogoProps) { - const themeColor = light ? '#fff' : '#2d3748'; - const accentColor = monotone ? themeColor : '#d22365'; + const themeColor = light ? '#fff' : '#2d3748' + const accentColor = monotone ? themeColor : '#d22365' return ( Nullstack @@ -52,8 +52,8 @@ export default function Logo({ light, height, monotone }: NullstackLogoProps) { fill={themeColor} /> diff --git a/nullstack.js b/nullstack.js index 56955b97..97d3ce20 100644 --- a/nullstack.js +++ b/nullstack.js @@ -1,3 +1,3 @@ -import Nullstack from "./{{NULLSTACK_ENVIRONMENT_NAME}}"; +import Nullstack from './{{NULLSTACK_ENVIRONMENT_NAME}}' -export default Nullstack; \ No newline at end of file +export default Nullstack diff --git a/package.json b/package.json index 8d8741fc..24cb8d6d 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,17 @@ "nullstack": "./scripts/index.js" }, "types": "./types/index.d.ts", + "scripts": { + "lint": "eslint \"**/*.{js,jsx,ts,tsx,njs,nts}\" --fix" + }, "dependencies": { "@babel/core": "^7.18.13", "@babel/parser": "7.17.12", - "@babel/preset-env": "^7.18.10", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-export-default-from": "^7.18.10", "@babel/plugin-transform-react-jsx": "^7.18.10", "@babel/plugin-transform-typescript": "^7.18.12", + "@babel/preset-env": "^7.18.10", "@babel/preset-react": "^7.18.6", "@babel/traverse": "7.17.12", "@swc/core": "1.2.179", @@ -29,6 +32,8 @@ "cors": "2.8.5", "css-loader": "6.7.1", "dotenv": "8.6.0", + "eslint-plugin-jest": "^27.1.6", + "eslint-plugin-nullstack": "^0.0.1", "express": "4.18.1", "fs-extra": "10.1.0", "mini-css-extract-plugin": "2.6.0", @@ -42,4 +47,4 @@ "webpack-dev-server": "4.9.0", "ws": "7.5.7" } -} \ No newline at end of file +} diff --git a/plugins/anchorable.js b/plugins/anchorable.js index 8769b4f9..92c5ae49 100644 --- a/plugins/anchorable.js +++ b/plugins/anchorable.js @@ -2,11 +2,7 @@ import noop from '../shared/noop' function match(node) { return ( - node && - node.type === 'a' && - node.attributes.href && - node.attributes.href.startsWith('/') && - !node.attributes.target + node && node.type === 'a' && node.attributes.href && node.attributes.href.startsWith('/') && !node.attributes.target ) } diff --git a/plugins/bindable.js b/plugins/bindable.js index f30a8a4b..b3094607 100644 --- a/plugins/bindable.js +++ b/plugins/bindable.js @@ -1,3 +1,4 @@ +/* eslint-disable no-undefined */ import noop from '../shared/noop' function match(node) { @@ -5,23 +6,23 @@ function match(node) { } function transform({ node, environment }) { - if (!match(node)) return; - const object = node.attributes.bind.object ?? {}; - const property = node.attributes.bind.property; + if (!match(node)) return + const object = node.attributes.bind.object ?? {} + const property = node.attributes.bind.property if (node.type === 'textarea') { - node.children = [object[property]]; + node.children = [object[property]] } else if (node.type === 'input' && node.attributes.type === 'checkbox') { - node.attributes.checked = object[property]; + node.attributes.checked = object[property] } else { - node.attributes.value = object[property] ?? ''; + node.attributes.value = object[property] ?? '' } if (environment.client) { if (node.attributes.type === 'checkbox' || node.attributes.type === 'radio') { - node.attributes.onclick ??= noop; + node.attributes.onclick ??= noop } else if (node.type !== 'input' && node.type !== 'textarea') { - node.attributes.onchange ??= noop; + node.attributes.onchange ??= noop } else { - node.attributes.oninput ??= noop; + node.attributes.oninput ??= noop } } } diff --git a/plugins/parameterizable.js b/plugins/parameterizable.js index 25700d16..a5e65c39 100644 --- a/plugins/parameterizable.js +++ b/plugins/parameterizable.js @@ -1,30 +1,26 @@ -import serializeParam from '../shared/serializeParam'; -import serializeSearch from '../shared/serializeSearch'; +import serializeParam from '../shared/serializeParam' +import serializeSearch from '../shared/serializeSearch' function match(node) { - return ( - node && - node.attributes && - (node.attributes.params || node.attributes.path) - ) + return node && node.attributes && (node.attributes.params || node.attributes.path) } -function transform({node, router, params}) { - if(!match(node)) return; - let serializedParams; - if(node.attributes.params) { - serializedParams = {}; - for(const key in node.attributes.params) { - serializedParams[key] = serializeParam(node.attributes.params[key]); +function transform({ node, router, params }) { + if (!match(node)) return + let serializedParams + if (node.attributes.params) { + serializedParams = {} + for (const key in node.attributes.params) { + serializedParams[key] = serializeParam(node.attributes.params[key]) } } else { - serializedParams = params; + serializedParams = params } - const search = serializeSearch(serializedParams); - const path = node.attributes.path || router.path; - node.attributes.href = path + (search ? '?' : '') + search; - delete node.attributes.path; - delete node.attributes.params; + const search = serializeSearch(serializedParams) + const path = node.attributes.path || router.path + node.attributes.href = path + (search ? '?' : '') + search + delete node.attributes.path + delete node.attributes.params } -export default { transform, client: true, server: true } \ No newline at end of file +export default { transform, client: true, server: true } diff --git a/plugins/routable.js b/plugins/routable.js index a8c20737..b7e0874c 100644 --- a/plugins/routable.js +++ b/plugins/routable.js @@ -1,37 +1,34 @@ -import routeMatches from '../shared/routeMatches'; +/* eslint-disable no-undefined */ +import routeMatches from '../shared/routeMatches' function erase(node) { - node.type = false; - delete node.attributes; - delete node.children; + node.type = false + delete node.attributes + delete node.children } function match(node) { - return ( - node && - node.attributes !== undefined && - node.attributes.route !== undefined - ) + return node && node.attributes !== undefined && node.attributes.route !== undefined } function load({ router }) { - router._routes = {}; + router._routes = {} } function transform({ node, depth, router }) { - if (!match(node)) return; + if (!match(node)) return const routeDepth = depth.slice(0, depth.lastIndexOf('-')) if (router._routes[routeDepth] !== undefined) { - erase(node); + erase(node) } else { - const params = routeMatches(router.url, node.attributes.route); + const params = routeMatches(router.url, node.attributes.route) if (params) { - router._routes[routeDepth] = true; - Object.assign(router._segments, params); + router._routes[routeDepth] = true + Object.assign(router._segments, params) } else { - erase(node); + erase(node) } } } -export default { load, transform, client: true, server: true } \ No newline at end of file +export default { load, transform, client: true, server: true } diff --git a/scripts/index.js b/scripts/index.js index e699ca24..566ecb92 100755 --- a/scripts/index.js +++ b/scripts/index.js @@ -1,14 +1,16 @@ #! /usr/bin/env node -const { program } = require('commander'); -const { version } = require('../package.json'); - -const webpack = require('webpack'); -const path = require('path'); -const { existsSync, readdir, unlink } = require('fs'); -const customConfig = path.resolve(process.cwd(), './webpack.config.js'); -const config = existsSync(customConfig) ? require(customConfig) : require('../webpack.config'); +/* eslint-disable no-continue */ +/* eslint-disable no-console */ +const { program } = require('commander') const dotenv = require('dotenv') +const { existsSync, readdir, unlink } = require('fs') const fetch = require('node-fetch') +const path = require('path') +const webpack = require('webpack') + +const { version } = require('../package.json') +const customConfig = path.resolve(process.cwd(), './webpack.config.js') +const config = existsSync(customConfig) ? require(customConfig) : require('../webpack.config') function getConfig(options) { return config.map((env) => env(null, options)) @@ -27,62 +29,59 @@ function loadEnv(env) { } function getFreePorts() { - return new Promise((resolve, reject) => { - const app1 = require('express')(); - const app2 = require('express')(); + return new Promise((resolve) => { + const app1 = require('express')() + const app2 = require('express')() const server1 = app1.listen(0, () => { const server2 = app2.listen(0, () => { - const ports = [ - server1.address().port, - server2.address().port - ] + const ports = [server1.address().port, server2.address().port] server1.close() server2.close() resolve(ports) - }); - }); + }) + }) }) } function getPort(port) { - return port || process.env['NULLSTACK_SERVER_PORT'] || process.env['PORT'] || 3000 + return port || process.env.NULLSTACK_SERVER_PORT || process.env.PORT || 3000 } function clearOutput(outputPath) { if (!existsSync(outputPath)) return readdir(outputPath, (err, files) => { - if (err) throw err; + if (err) throw err for (const file of files) { - if (file === '.cache') continue; - unlink(path.join(outputPath, file), err => { - if (err) throw err; - }); + if (file === '.cache') continue + unlink(path.join(outputPath, file), (errr) => { + if (errr) throw errr + }) } - }); + }) } async function start({ input, port, env, mode = 'spa', cold, disk, loader = 'swc' }) { const environment = 'development' - console.log(` 🚀️ Starting your application in ${environment} mode...`); + console.log(` 🚀️ Starting your application in ${environment} mode...`) loadEnv(env) - const WebpackDevServer = require('webpack-dev-server'); + const WebpackDevServer = require('webpack-dev-server') const { setLogLevel } = require('webpack/hot/log') setLogLevel('none') - process.env['NULLSTACK_ENVIRONMENT_MODE'] = mode - process.env['NULLSTACK_SERVER_PORT'] = getPort(port) + process.env.NULLSTACK_ENVIRONMENT_MODE = mode + process.env.NULLSTACK_SERVER_PORT = getPort(port) const ports = await getFreePorts() - process.env['NULSTACK_SERVER_PORT_YOU_SHOULD_NOT_CARE_ABOUT'] = ports[0] - process.env['NULSTACK_SERVER_SOCKET_PORT_YOU_SHOULD_NOT_CARE_ABOUT'] = ports[1] - process.env['NULLSTACK_ENVIRONMENT_HOT'] = (!cold).toString() - process.env['NULLSTACK_ENVIRONMENT_DISK'] = (!!disk).toString() - if (!process.env['NULLSTACK_PROJECT_DOMAIN']) process.env['NULLSTACK_PROJECT_DOMAIN'] = 'localhost' - if (!process.env['NULLSTACK_WORKER_PROTOCOL']) process.env['NULLSTACK_WORKER_PROTOCOL'] = 'http' - const target = `${process.env['NULLSTACK_WORKER_PROTOCOL']}://${process.env['NULLSTACK_PROJECT_DOMAIN']}:${process.env['NULSTACK_SERVER_PORT_YOU_SHOULD_NOT_CARE_ABOUT']}` + process.env.NULSTACK_SERVER_PORT_YOU_SHOULD_NOT_CARE_ABOUT = ports[0] + process.env.NULSTACK_SERVER_SOCKET_PORT_YOU_SHOULD_NOT_CARE_ABOUT = ports[1] + process.env.NULLSTACK_ENVIRONMENT_HOT = (!cold).toString() + process.env.NULLSTACK_ENVIRONMENT_DISK = (!!disk).toString() + if (!process.env.NULLSTACK_PROJECT_DOMAIN) process.env.NULLSTACK_PROJECT_DOMAIN = 'localhost' + if (!process.env.NULLSTACK_WORKER_PROTOCOL) process.env.NULLSTACK_WORKER_PROTOCOL = 'http' + const target = `${process.env.NULLSTACK_WORKER_PROTOCOL}://${process.env.NULLSTACK_PROJECT_DOMAIN}:${process.env.NULSTACK_SERVER_PORT_YOU_SHOULD_NOT_CARE_ABOUT}` const writeToDisk = disk ? true : (path) => path.includes('server') const devServerOptions = { hot: 'only', open: false, - host: process.env['NULLSTACK_PROJECT_DOMAIN'], + host: process.env.NULLSTACK_PROJECT_DOMAIN, devMiddleware: { index: false, stats: 'errors-only', @@ -93,7 +92,9 @@ async function start({ input, port, env, mode = 'spa', cold, disk, loader = 'swc logging: 'none', progress: false, reconnect: true, - webSocketURL: `${process.env['NULLSTACK_WORKER_PROTOCOL'].replace('http', 'ws')}://${process.env['NULLSTACK_PROJECT_DOMAIN']}:${process.env['NULLSTACK_SERVER_PORT']}/ws` + webSocketURL: `${process.env.NULLSTACK_WORKER_PROTOCOL.replace('http', 'ws')}://${ + process.env.NULLSTACK_PROJECT_DOMAIN + }:${process.env.NULLSTACK_SERVER_PORT}/ws`, }, proxy: { context: () => true, @@ -103,7 +104,7 @@ async function start({ input, port, env, mode = 'spa', cold, disk, loader = 'swc }, setupMiddlewares: (middlewares, devServer) => { if (!devServer) { - throw new Error('webpack-dev-server is not defined'); + throw new Error('webpack-dev-server is not defined') } middlewares.unshift(async (req, res, next) => { if (req.originalUrl.indexOf('.hot-update.') === -1) { @@ -129,38 +130,41 @@ async function start({ input, port, env, mode = 'spa', cold, disk, loader = 'swc } } waitForServer() - }); - return middlewares; + }) + return middlewares }, webSocketServer: require.resolve('./socket'), - port: process.env['NULLSTACK_SERVER_PORT'] - }; - const compiler = getCompiler({ environment, input, disk, loader }); + port: process.env.NULLSTACK_SERVER_PORT, + } + const compiler = getCompiler({ environment, input, disk, loader }) clearOutput(compiler.compilers[0].outputPath) - const server = new WebpackDevServer(devServerOptions, compiler); - const portChecker = require('express')().listen(process.env['NULLSTACK_SERVER_PORT'], () => { + const server = new WebpackDevServer(devServerOptions, compiler) + const portChecker = require('express')().listen(process.env.NULLSTACK_SERVER_PORT, () => { portChecker.close() server.startCallback(() => { - console.log('\x1b[36m%s\x1b[0m', ` ✅️ Your application is ready at http://${process.env['NULLSTACK_PROJECT_DOMAIN']}:${process.env['NULLSTACK_SERVER_PORT']}\n`); - }); + console.log( + '\x1b[36m%s\x1b[0m', + ` ✅️ Your application is ready at http://${process.env.NULLSTACK_PROJECT_DOMAIN}:${process.env.NULLSTACK_SERVER_PORT}\n`, + ) + }) }) } function build({ input, output, cache, env, mode = 'ssr' }) { - const environment = 'production'; - const compiler = getCompiler({ environment, input, cache }); + const environment = 'production' + const compiler = getCompiler({ environment, input, cache }) if (env) { - process.env['NULLSTACK_ENVIRONMENT_NAME'] = env; + process.env.NULLSTACK_ENVIRONMENT_NAME = env } - console.log(` 🚀️ Building your application in ${mode} mode...`); + console.log(` 🚀️ Building your application in ${mode} mode...`) compiler.run((error, stats) => { if (stats.hasErrors()) { console.log(stats.toString({ colors: true })) - process.exit(1); + process.exit(1) } - if (stats.hasErrors()) process.exit(1); - require(`../builders/${mode}`)({ output, cache, environment }); - }); + if (stats.hasErrors()) process.exit(1) + require(`../builders/${mode}`)({ output, cache, environment }) + }) } program @@ -190,8 +194,8 @@ program .action(build) program - .name("nullstack") + .name('nullstack') .addHelpCommand(false) .helpOption('-h, --help', 'Learn more about a specific command') .version(version, '-v, --version', 'Nullstack version being used') - .parse(process.argv); \ No newline at end of file + .parse(process.argv) diff --git a/scripts/socket.js b/scripts/socket.js index b1960028..72262918 100644 --- a/scripts/socket.js +++ b/scripts/socket.js @@ -1,70 +1,75 @@ -"use strict"; +/* eslint-disable no-empty-function */ +'use strict' -const WebSocket = require("ws"); -const BaseServer = require("webpack-dev-server/lib/servers/BaseServer"); +const BaseServer = require('webpack-dev-server/lib/servers/BaseServer') +const WebSocket = require('ws') + +class WebsocketServer extends BaseServer { -module.exports = class WebsocketServer extends BaseServer { constructor(server) { - super(server); + super(server) const options = { - ...(this.server.options.webSocketServer).options, + ...this.server.options.webSocketServer.options, clientTracking: false, - port: process.env['NULSTACK_SERVER_SOCKET_PORT_YOU_SHOULD_NOT_CARE_ABOUT'] - }; + port: process.env.NULSTACK_SERVER_SOCKET_PORT_YOU_SHOULD_NOT_CARE_ABOUT, + } - this.implementation = new WebSocket.Server(options); + this.implementation = new WebSocket.Server(options) - this.server.server.on("upgrade", (req, sock, head) => { + this.server.server.on('upgrade', (req, sock, head) => { if (!this.implementation.shouldHandle(req)) { - return; + return } this.implementation.handleUpgrade(req, sock, head, (connection) => { - this.implementation.emit("connection", connection, req); - }); - }); + this.implementation.emit('connection', connection, req) + }) + }) - this.implementation.on("error", (err) => { - this.server.logger.error(err.message); - }); + this.implementation.on('error', (err) => { + this.server.logger.error(err.message) + }) const interval = setInterval(() => { this.clients.forEach((client) => { if (client.isAlive === false) { - client.terminate(); + client.terminate() - return; + return } - client.isAlive = false; - client.ping(() => { }); - }); - }, 1000); + client.isAlive = false + client.ping(() => {}) + }) + }, 1000) - this.implementation.on("connection", (client) => { - this.clients.push(client); + this.implementation.on('connection', (client) => { + this.clients.push(client) - client.isAlive = true; + client.isAlive = true - client.on("message", (data) => { + client.on('message', (data) => { if (data === '{"type":"NULLSTACK_SERVER_STARTED"}') { - this.clients.forEach((client) => { - client.send('{"type":"NULLSTACK_SERVER_STARTED"}') - }); + this.clients.forEach((c) => { + c.send('{"type":"NULLSTACK_SERVER_STARTED"}') + }) } }) - client.on("pong", () => { - client.isAlive = true; - }); + client.on('pong', () => { + client.isAlive = true + }) - client.on("close", () => { - this.clients.splice(this.clients.indexOf(client), 1); - }); - }); + client.on('close', () => { + this.clients.splice(this.clients.indexOf(client), 1) + }) + }) - this.implementation.on("close", () => { - clearInterval(interval); - }); + this.implementation.on('close', () => { + clearInterval(interval) + }) } -}; + +} + +module.exports = WebsocketServer diff --git a/server/client.js b/server/client.js index c0cf1cde..89474ecd 100644 --- a/server/client.js +++ b/server/client.js @@ -1,14 +1,16 @@ +/* eslint-disable no-undefined */ +/* eslint-disable prefer-rest-params */ export function generateContext(context) { const contextProxyHandler = { set(target, name, value) { - context[name] = value; - return Reflect.set(...arguments); + context[name] = value + return Reflect.set(...arguments) }, get(target, name) { - return target[name] === undefined ? context[name] : target[name]; - } - } - return function(temporary) { - return new Proxy({...context, ...temporary}, contextProxyHandler); + return target[name] === undefined ? context[name] : target[name] + }, + } + return function (temporary) { + return new Proxy({ ...context, ...temporary }, contextProxyHandler) } -} \ No newline at end of file +} diff --git a/server/configurable.js b/server/configurable.js index 73bbbbc1..9bfb144d 100644 --- a/server/configurable.js +++ b/server/configurable.js @@ -1,13 +1,13 @@ -import { camelize } from '../shared/string'; +import { camelize } from '../shared/string' export function createConfigurable(label) { - const configurable = {}; + const configurable = {} for (const key in process.env) { - const lookup = `NULLSTACK_${label}_`; + const lookup = `NULLSTACK_${label}_` if (key.startsWith(lookup)) { - const camelCaseKey = camelize(key.substring(lookup.length)); - configurable[camelCaseKey] = process.env[key]; + const camelCaseKey = camelize(key.substring(lookup.length)) + configurable[camelCaseKey] = process.env[key] } } - return configurable; -} \ No newline at end of file + return configurable +} diff --git a/server/context.js b/server/context.js index b3278a80..9a76e45c 100644 --- a/server/context.js +++ b/server/context.js @@ -1,14 +1,15 @@ -const context = {}; +/* eslint-disable prefer-rest-params */ +const context = {} const contextProxyHandler = { set(target, name, value) { - context[name] = value; - return Reflect.set(...arguments); - } + context[name] = value + return Reflect.set(...arguments) + }, } export function generateContext(temporary) { - return new Proxy({...context, ...temporary}, contextProxyHandler); + return new Proxy({ ...context, ...temporary }, contextProxyHandler) } -export default context; \ No newline at end of file +export default context diff --git a/server/dotenv.js b/server/dotenv.js index 33838de1..6de25639 100644 --- a/server/dotenv.js +++ b/server/dotenv.js @@ -3,7 +3,7 @@ import dotenv from 'dotenv' let path = '.env' if (process.env.NULLSTACK_ENVIRONMENT_NAME) { - path += `.${process.env.NULLSTACK_ENVIRONMENT_NAME}` + path += `.${process.env.NULLSTACK_ENVIRONMENT_NAME}` } -dotenv.config({ path }) \ No newline at end of file +dotenv.config({ path }) diff --git a/server/environment.js b/server/environment.js index 7f74e2b4..345eaeba 100644 --- a/server/environment.js +++ b/server/environment.js @@ -1,18 +1,18 @@ -const environment = { client: false, server: true }; +const environment = { client: false, server: true } -environment.development = __dirname.indexOf('.development') > -1; -environment.production = !environment.development; +environment.development = __dirname.indexOf('.development') > -1 +environment.production = !environment.development -environment.mode = process.env.NULLSTACK_ENVIRONMENT_MODE || 'ssr'; +environment.mode = process.env.NULLSTACK_ENVIRONMENT_MODE || 'ssr' -environment.key = "{{NULLSTACK_ENVIRONMENT_KEY}}" +environment.key = '{{NULLSTACK_ENVIRONMENT_KEY}}' -environment.name = process.env.NULLSTACK_ENVIRONMENT_NAME || ''; +environment.name = process.env.NULLSTACK_ENVIRONMENT_NAME || '' if (environment.development) { environment.hot = process.env.NULLSTACK_ENVIRONMENT_HOT === 'true' } -Object.freeze(environment); +Object.freeze(environment) -export default environment; \ No newline at end of file +export default environment diff --git a/server/files.js b/server/files.js index 023225fe..daa5fc08 100644 --- a/server/files.js +++ b/server/files.js @@ -1,22 +1,23 @@ -import { readFileSync, existsSync } from 'fs'; -import environment from './environment'; -import path from 'path'; -import { generateIntegrity } from './integrities'; +import { existsSync, readFileSync } from 'fs' +import path from 'path' -const files = {}; +import environment from './environment' +import { generateIntegrity } from './integrities' + +const files = {} export function generateFile(file, server) { - if (files[file] && environment.production) return files[file]; + if (files[file] && environment.production) return files[file] const filePath = path.join(__dirname, file) if (existsSync(filePath)) { - files[file] = readFileSync(filePath, 'utf-8'); + files[file] = readFileSync(filePath, 'utf-8') } else { files[file] = '' } if (!server.less) { - generateIntegrity(file, files[file]); + generateIntegrity(file, files[file]) } - return files[file]; + return files[file] } -export default files; \ No newline at end of file +export default files diff --git a/server/generator.js b/server/generator.js index 0ef86436..4102962e 100644 --- a/server/generator.js +++ b/server/generator.js @@ -1,2 +1,2 @@ -const generator = {}; -export default generator; \ No newline at end of file +const generator = {} +export default generator diff --git a/server/index.js b/server/index.js index 87743ad0..27fc1efc 100644 --- a/server/index.js +++ b/server/index.js @@ -1,39 +1,41 @@ -import './dotenv'; -import { normalize } from 'path'; -import element from '../shared/element'; -import fragment from '../shared/fragment'; -import getProxyableMethods from '../shared/getProxyableMethods'; -import { useServerPlugins } from '../shared/plugins'; -import context from './context'; -import environment from './environment'; -import generator from './generator'; -import instanceProxyHandler from './instanceProxyHandler'; -import project from './project'; -import registry from './registry'; -import secrets from './secrets'; -import server from './server'; -import settings from './settings'; -import worker from './worker'; +/* eslint-disable no-continue */ +/* eslint-disable no-inner-declarations */ +import './dotenv' +import { normalize } from 'path' + +import element from '../shared/element' +import fragment from '../shared/fragment' +import getProxyableMethods from '../shared/getProxyableMethods' +import { useServerPlugins } from '../shared/plugins' +import context, { generateContext } from './context' +import environment from './environment' +import generator from './generator' +import instanceProxyHandler from './instanceProxyHandler' +import project from './project' +import registry from './registry' import reqres from './reqres' -import { generateContext } from './context'; +import secrets from './secrets' +import server from './server' +import settings from './settings' +import worker from './worker' globalThis.window = {} -context.server = server; -context.project = project; -context.environment = environment; -context.settings = settings; -context.secrets = secrets; -context.worker = worker; +context.server = server +context.project = project +context.environment = environment +context.settings = settings +context.secrets = secrets +context.worker = worker server.less = normalize(__filename) !== normalize(process.argv[1]) class Nullstack { - static registry = registry; - static element = element; - static fragment = fragment; - static use = useServerPlugins; + static registry = registry + static element = element + static fragment = fragment + static use = useServerPlugins static bindStaticFunctions(klass) { let parent = klass @@ -51,12 +53,12 @@ class Nullstack { } function _invoke(...args) { if (underscored) { - return klass[propName].call(klass, ...args); + return klass[propName].call(klass, ...args) } const params = args[0] || {} const { request, response } = reqres - const context = generateContext({ request, response, ...params }); - return klass[propName].call(klass, context); + const subcontext = generateContext({ request, response, ...params }) + return klass[propName].call(klass, subcontext) } klass[prop] = _invoke klass.prototype[prop] = _invoke @@ -68,9 +70,9 @@ class Nullstack { static start(Starter) { if (this.name.indexOf('Nullstack') > -1) { - generator.starter = () => element(Starter); + generator.starter = () => element(Starter) setTimeout(server.start, 0) - return context; + return context } } @@ -81,16 +83,16 @@ class Nullstack { key = null constructor() { - const methods = getProxyableMethods(this); - const proxy = new Proxy(this, instanceProxyHandler); + const methods = getProxyableMethods(this) + const proxy = new Proxy(this, instanceProxyHandler) for (const method of methods) { - this[method] = this[method].bind(proxy); + this[method] = this[method].bind(proxy) } - return proxy; + return proxy } toJSON() { - const serialized = {}; + const serialized = {} for (const name of Object.getOwnPropertyNames(this)) { if (name === 'prerendered') continue if (name === 'initiated') continue @@ -100,15 +102,15 @@ class Nullstack { if (name === '_attributes') continue if (name === '_scope') continue if (typeof this[name] === 'function') continue - serialized[name] = this[name]; + serialized[name] = this[name] } - return serialized; + return serialized } render() { - return false; + return false } } -export default Nullstack; \ No newline at end of file +export default Nullstack diff --git a/server/instanceProxyHandler.js b/server/instanceProxyHandler.js index 3daf4e60..ef7e794a 100644 --- a/server/instanceProxyHandler.js +++ b/server/instanceProxyHandler.js @@ -1,3 +1,4 @@ +/* eslint-disable prefer-rest-params */ const instanceProxyHandler = { get(target, name) { if (typeof target[name] === 'function' && name !== 'constructor') { @@ -5,12 +6,12 @@ const instanceProxyHandler = { return target[name].bind(target) } return (args) => { - const context = target._scope.generateContext({ ...target._attributes, ...args }); - return target[name](context); + const context = target._scope.generateContext({ ...target._attributes, ...args }) + return target[name](context) } } - return Reflect.get(...arguments); - } + return Reflect.get(...arguments) + }, } -export default instanceProxyHandler; \ No newline at end of file +export default instanceProxyHandler diff --git a/server/integrities.js b/server/integrities.js index 30635b06..4c34ba97 100644 --- a/server/integrities.js +++ b/server/integrities.js @@ -1,11 +1,12 @@ -import { createHash } from 'crypto'; +import { createHash } from 'crypto' + import environment from './environment' -const integrities = {}; +const integrities = {} export function generateIntegrity(key, source) { if (environment.development) return - integrities[key] = 'sha512-' + createHash('sha512', 'utf8').update(source).digest('base64'); + integrities[key] = `sha512-${createHash('sha512', 'utf8').update(source).digest('base64')}` } -export default integrities; \ No newline at end of file +export default integrities diff --git a/server/links.js b/server/links.js index 60019794..5951965a 100644 --- a/server/links.js +++ b/server/links.js @@ -1,24 +1,23 @@ -import { generateBase } from './project'; -import environment from './environment'; -import worker from './worker'; +import { generateBase } from './project' +import worker from './worker' export function absolute(path) { - if(path.indexOf('//') === -1) { - return `${generateBase()}${path}`; - } - return path; + if (path.indexOf('//') === -1) { + return `${generateBase()}${path}` + } + return path } export function cdn(path) { - if(!worker.cdn) return path; - if(path.indexOf('//') === -1) { - return `${worker.cdn}${path}`; - } - return path; + if (!worker.cdn) return path + if (path.indexOf('//') === -1) { + return `${worker.cdn}${path}` + } + return path } export function cdnOrAbsolute(path) { - if(path.indexOf('//') > -1) return path; - if(worker.cdn) return `${worker.cdn}${path}`; - return `${generateBase()}${path}`; -} \ No newline at end of file + if (path.indexOf('//') > -1) return path + if (worker.cdn) return `${worker.cdn}${path}` + return `${generateBase()}${path}` +} diff --git a/server/manifest.js b/server/manifest.js index 348a7a08..b7703061 100644 --- a/server/manifest.js +++ b/server/manifest.js @@ -1,41 +1,42 @@ -import { existsSync, readFileSync } from 'fs'; -import path from 'path'; -import project from './project'; -import { generateIntegrity } from './integrities'; -import files from './files'; -import { cdn } from './links'; +import { existsSync, readFileSync } from 'fs' +import path from 'path' + +import files from './files' +import { generateIntegrity } from './integrities' +import { cdn } from './links' +import project from './project' export default function generateManifest(server) { - if (files['manifest.webmanifest']) return files['manifest.webmanifest']; - const file = path.join(__dirname, '../', 'public', 'manifest.webmanifest'); + if (files['manifest.webmanifest']) return files['manifest.webmanifest'] + const file = path.join(__dirname, '../', 'public', 'manifest.webmanifest') if (existsSync(file)) { - return readFileSync(file, 'utf-8'); + return readFileSync(file, 'utf-8') } const json = { - "name": project.name, - "short_name": project.shortName || project.name, - "theme_color": project.color, - "background_color": project.backgroundColor || project.color, - "display": project.display, - "orientation": project.orientation, - "scope": project.scope, - "start_url": project.root, - "icons": [], - "splash_pages": null + name: project.name, + short_name: project.shortName || project.name, + theme_color: project.color, + background_color: project.backgroundColor || project.color, + display: project.display, + orientation: project.orientation, + scope: project.scope, + start_url: project.root, + icons: [], + splash_pages: null, } for (const size in project.icons) { - const icon = project.icons[size]; + const icon = project.icons[size] json.icons.push({ - "src": cdn(icon), - "sizes": `${size}x${size}`, - "type": "image/png", - "purpose": "maskable any" - }); + src: cdn(icon), + sizes: `${size}x${size}`, + type: 'image/png', + purpose: 'maskable any', + }) } - const manifest = JSON.stringify(json); + const manifest = JSON.stringify(json) if (!server.less) { - generateIntegrity('manifest.webmanifest', manifest); + generateIntegrity('manifest.webmanifest', manifest) } - files['manifest.webmanifest'] = manifest; - return manifest; -} \ No newline at end of file + files['manifest.webmanifest'] = manifest + return manifest +} diff --git a/server/params.js b/server/params.js index ce141386..6a3b2cba 100644 --- a/server/params.js +++ b/server/params.js @@ -1,13 +1,13 @@ -import getQueryStringParams from '../shared/getQueryStringParams'; +import getQueryStringParams from '../shared/getQueryStringParams' const paramsProxyHandler = { get(target, name) { - if(target[name] === false) return false; - return target[name] || ''; - } + if (target[name] === false) return false + return target[name] || '' + }, } export function generateParams(url) { - const params = getQueryStringParams(url); - return new Proxy(params, paramsProxyHandler); -} \ No newline at end of file + const params = getQueryStringParams(url) + return new Proxy(params, paramsProxyHandler) +} diff --git a/server/prerender.js b/server/prerender.js index b2905535..b6507080 100644 --- a/server/prerender.js +++ b/server/prerender.js @@ -1,66 +1,66 @@ -import environment from './environment'; -import project from './project'; -import Router from './router'; -import generator from './generator'; -import { generateParams } from './params'; -import render from './render'; -import settings from './settings'; -import worker from './worker'; -import printError from './printError'; -import { generateContext } from './client'; -import generateTree from '../shared/generateTree'; -import { loadPlugins } from '../shared/plugins'; +import generateTree from '../shared/generateTree' +import { loadPlugins } from '../shared/plugins' +import { generateContext } from './client' +import environment from './environment' +import generator from './generator' +import { generateParams } from './params' +import printError from './printError' +import project from './project' +import render from './render' +import Router from './router' +import settings from './settings' +import worker from './worker' export async function prerender(request, response) { - const context = {}; - context.page = { image: '/image-1200x630.png', status: 200 }; - context.project = project; - context.environment = environment; - context.settings = settings; - context.params = generateParams(request.originalUrl); - context.router = new Router(request, response); - const online = context.router.url !== `/nullstack/${environment.key}/offline`; - context.worker = { ...worker, online, responsive: online }; - const scope = {}; - scope.instances = {}; - context.instances = scope.instances; - context.router._segments = context.params; - scope.request = request; - scope.response = response; - scope.head = ''; - scope.body = ''; - scope.context = context; - scope.generateContext = generateContext(context); + const context = {} + context.page = { image: '/image-1200x630.png', status: 200 } + context.project = project + context.environment = environment + context.settings = settings + context.params = generateParams(request.originalUrl) + context.router = new Router(request, response) + const online = context.router.url !== `/nullstack/${environment.key}/offline` + context.worker = { ...worker, online, responsive: online } + const scope = {} + scope.instances = {} + context.instances = scope.instances + context.router._segments = context.params + scope.request = request + scope.response = response + scope.head = '' + scope.body = '' + scope.context = context + scope.generateContext = generateContext(context) scope.nextBody = {} scope.nextHead = [] - scope.plugins = loadPlugins(scope); + scope.plugins = loadPlugins(scope) try { if (environment.production || environment.mode !== 'spa') { - const tree = await generateTree(generator.starter(), scope); - scope.body = render(tree, scope); + const tree = await generateTree(generator.starter(), scope) + scope.body = render(tree, scope) } if (!online) { - context.page.status = 200; + context.page.status = 200 } } catch (error) { - printError(error); - context.page.status = 500; + printError(error) + context.page.status = 500 } finally { if (context.page.status !== 200) { scope.nextBody = {} scope.nextHead = [] for (const key in context.router._routes) { - delete context.router._routes[key]; + delete context.router._routes[key] } for (const key in scope.instances) { - delete scope.instances[key]; + delete scope.instances[key] } - scope.head = ''; - scope.plugins = loadPlugins(scope); - const tree = await generateTree(generator.starter(), scope); - scope.body = render(tree, scope); + scope.head = '' + scope.plugins = loadPlugins(scope) + const tree = await generateTree(generator.starter(), scope) + scope.body = render(tree, scope) } } - return scope; -} \ No newline at end of file + return scope +} diff --git a/server/printError.js b/server/printError.js index 43a2e067..2568e4b0 100644 --- a/server/printError.js +++ b/server/printError.js @@ -1,42 +1,44 @@ +/* eslint-disable no-console */ export default function (error) { - const lines = error.stack.split(`\n`); - let initiator = lines.find((line) => line.indexOf('Proxy') > -1); + const lines = error.stack.split(`\n`) + let initiator = lines.find((line) => line.indexOf('Proxy') > -1) if (initiator) { - initiator = initiator.split('(')[0]; + initiator = initiator.split('(')[0] if (initiator) { - initiator = initiator.trim(); - initiator = initiator.replace('at', '').trim(); + initiator = initiator.trim() + initiator = initiator.replace('at', '').trim() } } - let source = lines.find((line) => line.indexOf('webpack:') > -1); + let source = lines.find((line) => line.indexOf('webpack:') > -1) if (source) { - source = source.split('webpack:')[1]; + source = source.split('webpack:')[1] if (source) { - source = source.split('\\').join('/'); + source = source.split('\\').join('/') } } - let file, line; + let file, line if (source) { - [file, line] = source.split(':'); - let className = file.split('/').find((segment) => segment.indexOf('.') > -1); + // eslint-disable-next-line nullstack/prettier + [file, line] = source.split(':') + let className = file.split('/').find((segment) => segment.indexOf('.') > -1) if (className) { - className = className.replace('.njs', ''); + className = className.replace('.njs', '') if (initiator) { - initiator = initiator.replace('Proxy', className); + initiator = initiator.replace('Proxy', className) } } } - console.log(); - console.log('\x1b[31m', error.name, '-', error.message, '\x1b[0m'); - console.log(); + console.log() + console.log('\x1b[31m', error.name, '-', error.message, '\x1b[0m') + console.log() if (initiator) { - console.log('\x1b[2m', 'initiator:', '\x1b[0m', '\x1b[37m', initiator, '\x1b[0m'); + console.log('\x1b[2m', 'initiator:', '\x1b[0m', '\x1b[37m', initiator, '\x1b[0m') } if (file) { - console.log('\x1b[2m', 'file: ', '\x1b[0m', '\x1b[37m', file, '\x1b[0m'); + console.log('\x1b[2m', 'file: ', '\x1b[0m', '\x1b[37m', file, '\x1b[0m') } if (line) { - console.log('\x1b[2m', 'line: ', '\x1b[0m', '\x1b[37m', line, '\x1b[0m'); + console.log('\x1b[2m', 'line: ', '\x1b[0m', '\x1b[37m', line, '\x1b[0m') } - console.log(); -} \ No newline at end of file + console.log() +} diff --git a/server/project.js b/server/project.js index 83915cbf..b5787720 100644 --- a/server/project.js +++ b/server/project.js @@ -1,36 +1,36 @@ -import environment from './environment'; -import worker from './worker'; -import reqres from './reqres'; +import environment from './environment' +import reqres from './reqres' +import worker from './worker' -const project = {}; +const project = {} -project.domain = process.env['NULLSTACK_PROJECT_DOMAIN'] -project.name = process.env['NULLSTACK_PROJECT_NAME'] -project.shortName = process.env['NULLSTACK_PROJECT_SHORT_NAME'] -project.color = process.env['NULLSTACK_PROJECT_COLOR'] -project.viewport = process.env['NULLSTACK_PROJECT_VIEWPORT'] || 'width=device-width, initial-scale=1, shrink-to-fit=no'; -project.type = 'website'; -project.display = 'standalone'; -project.orientation = 'portrait'; -project.scope = '/'; -project.root = '/'; -project.sitemap = environment.mode === 'ssg'; -project.favicon = '/favicon-96x96.png'; -project.disallow = []; -project.icons = JSON.parse(`{{NULLSTACK_PROJECT_ICONS}}`); +project.domain = process.env.NULLSTACK_PROJECT_DOMAIN +project.name = process.env.NULLSTACK_PROJECT_NAME +project.shortName = process.env.NULLSTACK_PROJECT_SHORT_NAME +project.color = process.env.NULLSTACK_PROJECT_COLOR +project.viewport = process.env.NULLSTACK_PROJECT_VIEWPORT || 'width=device-width, initial-scale=1, shrink-to-fit=no' +project.type = 'website' +project.display = 'standalone' +project.orientation = 'portrait' +project.scope = '/' +project.root = '/' +project.sitemap = environment.mode === 'ssg' +project.favicon = '/favicon-96x96.png' +project.disallow = [] +project.icons = JSON.parse(`{{NULLSTACK_PROJECT_ICONS}}`) function getHost() { if (reqres.request?.headers?.host) { return reqres.request.headers.host } if (project.domain === 'localhost') { - return `localhost:${process.env['NULLSTACK_SERVER_PORT']}` + return `localhost:${process.env.NULLSTACK_SERVER_PORT}` } return project.domain } export function generateBase() { - return `${worker.protocol}://${getHost()}`; + return `${worker.protocol}://${getHost()}` } -export default project; \ No newline at end of file +export default project diff --git a/server/registry.js b/server/registry.js index dee1de3e..5305b2bd 100644 --- a/server/registry.js +++ b/server/registry.js @@ -1,2 +1,2 @@ -const registry = {}; -export default registry; \ No newline at end of file +const registry = {} +export default registry diff --git a/server/render.js b/server/render.js index a67f2d18..d9d5f441 100644 --- a/server/render.js +++ b/server/render.js @@ -1,53 +1,54 @@ -import { isFalse } from "../shared/nodes"; -import { sanitizeHtml } from "../shared/sanitizeString"; -import renderAttributes from "./renderAttributes"; +/* eslint-disable no-continue */ +import { isFalse } from '../shared/nodes' +import { sanitizeHtml } from '../shared/sanitizeString' +import renderAttributes from './renderAttributes' function isSelfClosing(type) { - if (type === 'input') return true; - if (type === 'img') return true; - if (type === 'link') return true; - if (type === 'meta') return true; - if (type === 'br') return true; - if (type === 'hr') return true; - if (type === 'area') return true; - if (type === 'base') return true; - if (type === 'col') return true; - if (type === 'embed') return true; - if (type === 'param') return true; - if (type === 'source') return true; - if (type === 'track') return true; - if (type === 'wbr') return true; - if (type === 'menuitem') return true; - return false; + if (type === 'input') return true + if (type === 'img') return true + if (type === 'link') return true + if (type === 'meta') return true + if (type === 'br') return true + if (type === 'hr') return true + if (type === 'area') return true + if (type === 'base') return true + if (type === 'col') return true + if (type === 'embed') return true + if (type === 'param') return true + if (type === 'source') return true + if (type === 'track') return true + if (type === 'wbr') return true + if (type === 'menuitem') return true + return false } function renderBody(node, scope, next) { if (isFalse(node)) { - return ""; + return '' } if (node.type === 'text') { const text = node.text === '' ? ' ' : sanitizeHtml(node.text.toString()) - return next && next.type === 'text' ? text + "" : text + return next && next.type === 'text' ? `${text}` : text } - let element = `<${node.type}`; + let element = `<${node.type}` element += renderAttributes(node.attributes) if (isSelfClosing(node.type)) { - element += '/>'; + element += '/>' } else { - element += '>'; + element += '>' if (node.attributes.html) { - const source = node.attributes.html; - element += source; + const source = node.attributes.html + element += source } else if (node.type === 'textarea') { - element += node.children[0].text; + element += node.children[0].text } else { for (let i = 0; i < node.children.length; i++) { - element += renderBody(node.children[i], scope, node.children[i + 1]); + element += renderBody(node.children[i], scope, node.children[i + 1]) } } - element += ``; + element += `` } - return element; + return element } function renderHead(scope) { @@ -55,14 +56,14 @@ function renderHead(scope) { for (let i = 0; i < limit; i++) { const node = scope.nextHead[i] if (isFalse(node)) { - continue; + continue } - scope.head += `<${node.type}`; + scope.head += `<${node.type}` scope.head += renderAttributes(node.attributes) if (isSelfClosing(node.type)) { - scope.head += '/>'; + scope.head += '/>' } else { - scope.head += '>'; + scope.head += '>' scope.head += node.attributes.html scope.head += `` } @@ -72,4 +73,4 @@ function renderHead(scope) { export default function render(node, scope, next) { renderHead(scope) return renderBody(node, scope, next) -} \ No newline at end of file +} diff --git a/server/renderAttributes.js b/server/renderAttributes.js index 01e5a37a..60a4cced 100644 --- a/server/renderAttributes.js +++ b/server/renderAttributes.js @@ -1,25 +1,26 @@ -import generateTruthyString from "../shared/generateTruthyString"; +/* eslint-disable no-continue */ +import generateTruthyString from '../shared/generateTruthyString' export default function renderAttributes(attributes) { let element = '' - for (let name in attributes) { + for (const name in attributes) { if (name === 'debounce') continue if (!name.startsWith('on') && name !== 'html') { - let attribute = attributes[name]; + let attribute = attributes[name] if ((name === 'class' || name === 'style') && Array.isArray(attributes[name])) { - attribute = generateTruthyString(attributes[name]); + attribute = generateTruthyString(attributes[name]) } else { - attribute = attributes[name]; + attribute = attributes[name] } - const type = typeof attribute; + const type = typeof attribute if (type !== 'object' && type !== 'function') { - if (name != 'value' && attribute === true) { - element += ` ${name}`; + if (name !== 'value' && attribute === true) { + element += ` ${name}` } else if (name === 'value' || (attribute !== false && attribute !== null && attribute !== undefined)) { - element += ` ${name}="${attribute}"`; + element += ` ${name}="${attribute}"` } } } } return element -} \ No newline at end of file +} diff --git a/server/reqres.js b/server/reqres.js index 1e86fc66..c743ad43 100644 --- a/server/reqres.js +++ b/server/reqres.js @@ -1,2 +1,2 @@ const reqres = {} -export default reqres \ No newline at end of file +export default reqres diff --git a/server/robots.js b/server/robots.js index 6c90e04f..760258a5 100644 --- a/server/robots.js +++ b/server/robots.js @@ -1,25 +1,25 @@ -import project, { generateBase } from './project'; -import files from './files'; +import files from './files' +import project, { generateBase } from './project' export default function generateRobots() { - if(files['robots.txt']) return files['robots.txt']; + if (files['robots.txt']) return files['robots.txt'] const lines = [] - lines.push('User-Agent: *'); - if(!project.disallow.includes('/')) { - lines.push(`Allow: ${project.root}`); + lines.push('User-Agent: *') + if (!project.disallow.includes('/')) { + lines.push(`Allow: ${project.root}`) } - for(const path of project.disallow) { - lines.push(`Disallow: ${path}`); + for (const path of project.disallow) { + lines.push(`Disallow: ${path}`) } - if(project.sitemap) { - if(project.sitemap === true) { - lines.push(`Sitemap: ${generateBase()}/sitemap.xml`); + if (project.sitemap) { + if (project.sitemap === true) { + lines.push(`Sitemap: ${generateBase()}/sitemap.xml`) } else if (project.sitemap.indexOf('//') === -1) { - lines.push(`Sitemap: ${generateBase()}${project.sitemap}`); + lines.push(`Sitemap: ${generateBase()}${project.sitemap}`) } else { - lines.push(`Sitemap: ${project.sitemap}`); + lines.push(`Sitemap: ${project.sitemap}`) } } - files['robots.txt'] = lines.join(`\n`); + files['robots.txt'] = lines.join(`\n`) return files['robots.txt'] -} \ No newline at end of file +} diff --git a/server/router.js b/server/router.js index 270fa71a..940925e8 100644 --- a/server/router.js +++ b/server/router.js @@ -1,39 +1,39 @@ -import extractLocation from '../shared/extractLocation'; -import { generateBase } from './project'; +import extractLocation from '../shared/extractLocation' +import { generateBase } from './project' export default class Router { previous = null constructor(request, response) { - this.request = request; - this.response = response; + this.request = request + this.response = response } _redirect(target) { if (!this.response.headersSent) { - const { url } = extractLocation(target); - this.response.redirect(url); + const { url } = extractLocation(target) + this.response.redirect(url) } } get url() { - return extractLocation(this.request.originalUrl).url; + return extractLocation(this.request.originalUrl).url } set url(target) { - this._redirect(target); + this._redirect(target) } get path() { - return extractLocation(this.request.path).path; + return extractLocation(this.request.path).path } set path(target) { - const { search } = extractLocation(this.request.originalUrl); + const { search } = extractLocation(this.request.originalUrl) if (search) { - this._redirect(target + '?' + search); + this._redirect(`${target}?${search}`) } else { - this._redirect(target); + this._redirect(target) } } diff --git a/server/secrets.js b/server/secrets.js index d1ef3b32..e8fd909b 100644 --- a/server/secrets.js +++ b/server/secrets.js @@ -1,5 +1,5 @@ -import { createConfigurable } from './configurable'; +import { createConfigurable } from './configurable' -const secrets = createConfigurable('SECRETS'); +const secrets = createConfigurable('SECRETS') -export default secrets; +export default secrets diff --git a/server/server.js b/server/server.js index 7a3c5ed9..f3acb5cf 100644 --- a/server/server.js +++ b/server/server.js @@ -1,32 +1,40 @@ -import bodyParser from 'body-parser'; -import cors from 'cors'; -import express from 'express'; -import fetch from 'node-fetch'; -import path from 'path'; -import deserialize from '../shared/deserialize'; -import prefix from '../shared/prefix'; -import context, { generateContext } from './context'; -import environment from './environment'; -import { generateFile } from './files'; -import generateManifest from './manifest'; -import { prerender } from './prerender'; -import printError from './printError'; -import registry from './registry'; -import generateRobots from './robots'; -import template from './template'; -import { generateServiceWorker } from './worker'; -import reqres from './reqres' -import WebSocket from 'ws'; +/* eslint-disable no-console */ +/* eslint-disable no-undefined */ +/* eslint-disable no-empty-function */ +import bodyParser from 'body-parser' +import cors from 'cors' +import express from 'express' import { writeFileSync } from 'fs' -import extractParamValue from '../shared/extractParamValue'; +import fetch from 'node-fetch' +import path from 'path' +import WebSocket from 'ws' + +import deserialize from '../shared/deserialize' +import extractParamValue from '../shared/extractParamValue' +import prefix from '../shared/prefix' +import context, { generateContext } from './context' +import environment from './environment' +import { generateFile } from './files' +import generateManifest from './manifest' +import { prerender } from './prerender' +import printError from './printError' +import registry from './registry' +import reqres from './reqres' +import generateRobots from './robots' +import template from './template' +import { generateServiceWorker } from './worker' if (!global.fetch) { - global.fetch = fetch; + global.fetch = fetch } -const server = express(); +const server = express() -server.port = process.env['NULSTACK_SERVER_PORT_YOU_SHOULD_NOT_CARE_ABOUT'] || process.env['NULLSTACK_SERVER_PORT'] || process.env['PORT'] || 3000 +server.port = + process.env.NULSTACK_SERVER_PORT_YOU_SHOULD_NOT_CARE_ABOUT || + process.env.NULLSTACK_SERVER_PORT || + process.env.PORT || + 3000 let contextStarted = false let serverStarted = false @@ -44,15 +52,15 @@ for (const method of ['get', 'post', 'put', 'patch', 'delete', 'all']) { params[key] = extractParamValue(request.query[key]) } if (request.method !== 'GET') { - Object.assign(params, deserialize(request.body)); + Object.assign(params, deserialize(request.body)) } try { - const context = generateContext({ request, response, ...params }); - const result = await args[1](context); - response.json(result); + const subcontext = generateContext({ request, response, ...params }) + const result = await args[1](subcontext) + response.json(result) } catch (error) { - printError(error); - response.status(500).json({}); + printError(error) + response.status(500).json({}) } }) } else { @@ -63,19 +71,19 @@ for (const method of ['get', 'post', 'put', 'patch', 'delete', 'all']) { server.use(async (request, response, next) => { if (!contextStarted) { - typeof context.start === 'function' && await context.start(); - contextStarted = true; + typeof context.start === 'function' && (await context.start()) + contextStarted = true } next() }) -function createRequest(path) { +function createRequest(url) { return { - method: "GET", - host: "", + method: 'GET', + host: '', cookies: {}, query: {}, - url: path, + url, headers: {}, } } @@ -91,183 +99,185 @@ function createResponse(callback) { set statusCode(status) { this._statusCode = status this.status(status) - } - }; - const headers = {}; - let code = 200; + }, + } + const headers = {} + let code = 200 res.set = res.header = (x, y) => { if (arguments.length === 2) { - res.setHeader(x, y); + res.setHeader(x, y) } else { for (const key in x) { - res.setHeader(key, x[key]); + res.setHeader(key, x[key]) } } - return res; + return res } res.setHeader = (x, y) => { - headers[x] = y; - headers[x.toLowerCase()] = y; - return res; - }; - res.getHeader = (x) => headers[x]; + headers[x] = y + headers[x.toLowerCase()] = y + return res + } + res.getHeader = (x) => headers[x] res.redirect = function (_code, url) { if (typeof _code !== 'number') { - code = 301; - url = _code; + code = 301 + url = _code } else { - code = _code; + code = _code } - res.setHeader("Location", url); - res.end(); - }; + res.setHeader('Location', url) + res.end() + } res.status = res.sendStatus = function (number) { - code = number; - return res; - }; - res.end = res.send = res.write = function (data = '') { - if (callback) callback(code, data, headers); - }; - return res; + code = number + return res + } + res.end = + res.send = + res.write = + function (data = '') { + if (callback) callback(code, data, headers) + } + return res } -server.prerender = async function (originalUrl, options) { +server.prerender = async function (originalUrl) { server.start() - return new Promise((resolve, reject) => { + return new Promise((resolve) => { server._router.handle( createRequest(originalUrl), - createResponse((code, data, headers) => resolve(data)), - () => { } + createResponse((code, data) => resolve(data)), + () => {}, ) }) } server.start = function () { + if (serverStarted) return + serverStarted = true - if (serverStarted) return; - serverStarted = true; + server.use(cors(server.cors)) - server.use(cors(server.cors)); + server.use(express.static(path.join(__dirname, '..', 'public'))) - server.use(express.static(path.join(__dirname, '..', 'public'))); - - server.use(bodyParser.text({ limit: server.maximumPayloadSize })); + server.use(bodyParser.text({ limit: server.maximumPayloadSize })) if (environment.production) { - server.get(`/:number.client.js`, (request, response) => { - response.setHeader('Cache-Control', 'max-age=31536000, immutable'); - response.contentType('text/javascript'); - response.send(generateFile(`${request.params.number}.client.js`, server)); - }); + response.setHeader('Cache-Control', 'max-age=31536000, immutable') + response.contentType('text/javascript') + response.send(generateFile(`${request.params.number}.client.js`, server)) + }) server.get(`/:number.client.css`, (request, response) => { - response.setHeader('Cache-Control', 'max-age=31536000, immutable'); - response.contentType('text/css'); - response.send(generateFile(`${request.params.number}.client.css`, server)); - }); + response.setHeader('Cache-Control', 'max-age=31536000, immutable') + response.contentType('text/css') + response.send(generateFile(`${request.params.number}.client.css`, server)) + }) server.get(`/client.css`, (request, response) => { - response.setHeader('Cache-Control', 'max-age=31536000, immutable'); - response.contentType('text/css'); - response.send(generateFile('client.css', server)); - }); + response.setHeader('Cache-Control', 'max-age=31536000, immutable') + response.contentType('text/css') + response.send(generateFile('client.css', server)) + }) server.get(`/client.js`, (request, response) => { - response.setHeader('Cache-Control', 'max-age=31536000, immutable'); - response.contentType('text/javascript'); - response.send(generateFile('client.js', server)); - }); - + response.setHeader('Cache-Control', 'max-age=31536000, immutable') + response.contentType('text/javascript') + response.send(generateFile('client.js', server)) + }) } server.get(`/manifest.webmanifest`, (request, response) => { - response.setHeader('Cache-Control', 'max-age=31536000, immutable'); - response.contentType('application/manifest+json'); - response.send(generateManifest(server)); - }); + response.setHeader('Cache-Control', 'max-age=31536000, immutable') + response.contentType('application/manifest+json') + response.send(generateManifest(server)) + }) server.get(`/service-worker.js`, (request, response) => { - response.setHeader('Cache-Control', 'max-age=31536000, immutable'); - response.contentType('text/javascript'); - response.send(generateServiceWorker()); - }); + response.setHeader('Cache-Control', 'max-age=31536000, immutable') + response.contentType('text/javascript') + response.send(generateServiceWorker()) + }) server.get('/robots.txt', (request, response) => { - response.send(generateRobots()); - }); + response.send(generateRobots()) + }) server.all(`/${prefix}/:hash/:methodName.json`, async (request, response) => { - const payload = request.method === 'GET' ? request.query.payload : request.body; - const args = deserialize(payload); - const { hash, methodName } = request.params; - const [invokerHash, boundHash] = hash.split('-'); - const key = `${invokerHash}.${methodName}`; - const invokerKlass = registry[invokerHash]; - let boundKlass = invokerKlass; + const payload = request.method === 'GET' ? request.query.payload : request.body + const args = deserialize(payload) + const { hash, methodName } = request.params + const [invokerHash, boundHash] = hash.split('-') + const key = `${invokerHash}.${methodName}` + const invokerKlass = registry[invokerHash] + let boundKlass = invokerKlass if (boundHash) { - boundKlass = registry[boundHash]; + boundKlass = registry[boundHash] if (!(boundKlass.prototype instanceof invokerKlass)) { - return response.status(401).json({}); + return response.status(401).json({}) } } - const method = registry[key]; + const method = registry[key] if (method !== undefined) { try { - const context = generateContext({ request, response, ...args }); - const result = await method.call(boundKlass, context); - response.json({ result }); + const subcontext = generateContext({ request, response, ...args }) + const result = await method.call(boundKlass, subcontext) + response.json({ result }) } catch (error) { - printError(error); - response.status(500).json({}); + printError(error) + response.status(500).json({}) } } else { - response.status(404).json({}); + response.status(404).json({}) } - }); + }) server.get('*', async (request, response, next) => { if (request.originalUrl.split('?')[0].indexOf('.') > -1) { - return next(); + return next() } reqres.request = request reqres.response = response - const scope = await prerender(request, response); + const scope = await prerender(request, response) if (!response.headersSent) { - const status = scope.context.page.status; - const html = template(scope); + const status = scope.context.page.status + const html = template(scope) reqres.request = null reqres.response = null - response.status(status).send(html); + response.status(status).send(html) } else { reqres.request = null reqres.response = null } - }); + }) if (!server.less) { if (!server.port) { - console.log('\x1b[31mServer port is not defined!\x1b[0m'); - process.exit(); + console.log('\x1b[31mServer port is not defined!\x1b[0m') + process.exit() } server.listen(server.port, async () => { if (environment.development) { - if (process.env['NULLSTACK_ENVIRONMENT_DISK'] === 'true') { - const content = await server.prerender('/'); - const target = process.cwd() + `/.development/index.html` + if (process.env.NULLSTACK_ENVIRONMENT_DISK === 'true') { + const content = await server.prerender('/') + const target = `${process.cwd()}/.development/index.html` writeFileSync(target, content) } - const socket = new WebSocket(`ws://localhost:${process.env['NULLSTACK_SERVER_PORT']}/ws`); + const socket = new WebSocket(`ws://localhost:${process.env.NULLSTACK_SERVER_PORT}/ws`) socket.onopen = async function (e) { socket.send('{"type":"NULLSTACK_SERVER_STARTED"}') } } else { - console.log('\x1b[36m%s\x1b[0m', ` ✅️ Your application is ready at http://${process.env['NULLSTACK_PROJECT_DOMAIN']}:${process.env['NULLSTACK_SERVER_PORT']}\n`); + console.log( + '\x1b[36m%s\x1b[0m', + ` ✅️ Your application is ready at http://${process.env.NULLSTACK_PROJECT_DOMAIN}:${process.env.NULLSTACK_SERVER_PORT}\n`, + ) } - }); + }) } - } -export default server; +export default server diff --git a/server/settings.js b/server/settings.js index 14446d40..17192b09 100644 --- a/server/settings.js +++ b/server/settings.js @@ -1,5 +1,5 @@ -import { createConfigurable } from './configurable'; +import { createConfigurable } from './configurable' -const secrets = createConfigurable('SETTINGS'); +const secrets = createConfigurable('SETTINGS') -export default secrets; \ No newline at end of file +export default secrets diff --git a/server/template.js b/server/template.js index e4c2879c..c405e42a 100644 --- a/server/template.js +++ b/server/template.js @@ -1,34 +1,39 @@ -import { sanitizeString } from '../shared/sanitizeString'; -import environment from './environment'; -import integrities from './integrities'; -import { absolute, cdn, cdnOrAbsolute } from './links'; -import project from './project'; -import settings from './settings'; -import renderAttributes from './renderAttributes'; +import { sanitizeString } from '../shared/sanitizeString' +import environment from './environment' +import integrities from './integrities' +import { absolute, cdn, cdnOrAbsolute } from './links' +import project from './project' +import renderAttributes from './renderAttributes' +import settings from './settings' export default function ({ head, body, nextBody, context, instances }) { - const { page, router, worker, params } = context; - const canonical = absolute(page.canonical || router.url); - const image = cdnOrAbsolute(page.image); - const serializableContext = {}; - const blacklist = ['scope', 'router', 'page', 'environment', 'settings', 'worker', 'params', 'project', 'instances']; + const { page, router, worker, params } = context + const canonical = absolute(page.canonical || router.url) + const image = cdnOrAbsolute(page.image) + const serializableContext = {} + const blacklist = ['scope', 'router', 'page', 'environment', 'settings', 'worker', 'params', 'project', 'instances'] for (const [key, value] of Object.entries(context)) { if (!blacklist.includes(key) && typeof value !== 'function') { - serializableContext[key] = value; + serializableContext[key] = value } } - const serializableInstances = {}; + const serializableInstances = {} for (const [key, value] of Object.entries(instances)) { if (Object.keys(value).length) { - serializableInstances[key] = value; + serializableInstances[key] = value } } const state = { - page, environment, settings, worker, params, project, + page, + environment, + settings, + worker, + params, + project, instances: environment.mode === 'spa' ? {} : serializableInstances, - context: environment.mode === 'spa' ? {} : serializableContext - }; - return (` + context: environment.mode === 'spa' ? {} : serializableContext, + } + return ` @@ -53,16 +58,20 @@ export default function ({ head, body, nextBody, context, instances }) { ${page.robots ? `` : ''} ${project.viewport ? `` : ''} - + ${page.schema ? `` : ''} ${project.icons['180'] ? `` : ''} ${head.split('').join('')} - + ${environment.mode === 'spa' ? '
' : body} -`) -} \ No newline at end of file +` +} diff --git a/server/worker.js b/server/worker.js index 922af9a5..ab42fd68 100644 --- a/server/worker.js +++ b/server/worker.js @@ -1,79 +1,83 @@ -import activate from '../workers/activate.js?raw'; -import cacheFirst from '../workers/cacheFirst.js?raw'; -import dynamicFetch from '../workers/dynamicFetch.js?raw'; -import dynamicInstall from '../workers/dynamicInstall.js?raw'; -import load from '../workers/load.js?raw'; -import networkDataFirst from '../workers/networkDataFirst.js?raw'; -import networkFirst from '../workers/networkFirst.js?raw'; -import staleWhileRevalidate from '../workers/staleWhileRevalidate.js?raw'; -import staticFetch from '../workers/staticFetch.js?raw'; -import staticHelpers from '../workers/staticHelpers.js?raw'; -import staticInstall from '../workers/staticInstall.js?raw'; -import { existsSync, readdirSync, readFileSync } from 'fs'; -import path from 'path'; -import environment from './environment'; -import files from './files'; -import project from './project'; -import settings from './settings'; +/* eslint-disable import/no-unresolved */ +import { existsSync, readdirSync, readFileSync } from 'fs' +import path from 'path' -const worker = {}; +import activate from '../workers/activate.js?raw' +import cacheFirst from '../workers/cacheFirst.js?raw' +import dynamicFetch from '../workers/dynamicFetch.js?raw' +import dynamicInstall from '../workers/dynamicInstall.js?raw' +import load from '../workers/load.js?raw' +import networkDataFirst from '../workers/networkDataFirst.js?raw' +import networkFirst from '../workers/networkFirst.js?raw' +import staleWhileRevalidate from '../workers/staleWhileRevalidate.js?raw' +import staticFetch from '../workers/staticFetch.js?raw' +import staticHelpers from '../workers/staticHelpers.js?raw' +import staticInstall from '../workers/staticInstall.js?raw' +import environment from './environment' +import files from './files' +import project from './project' +import settings from './settings' -worker.enabled = environment.production; -worker.fetching = false; -worker.preload = []; -worker.headers = {}; +const worker = {} + +worker.enabled = environment.production +worker.fetching = false +worker.preload = [] +worker.headers = {} worker.api = process.env.NULLSTACK_WORKER_API ?? '' worker.cdn = process.env.NULLSTACK_WORKER_CDN ?? '' -worker.protocol = process.env.NULLSTACK_WORKER_PROTOCOL ?? (environment.development ? 'http' : 'https'); +worker.protocol = process.env.NULLSTACK_WORKER_PROTOCOL ?? (environment.development ? 'http' : 'https') -const emptyQueue = Object.freeze([]); +const emptyQueue = Object.freeze([]) const queuesProxyHandler = { get() { - return emptyQueue; - } + return emptyQueue + }, } -worker.queues = new Proxy({}, queuesProxyHandler); +worker.queues = new Proxy({}, queuesProxyHandler) export function generateServiceWorker() { - if (files['service-worker.js']) return files['service-worker.js']; - const sources = []; - const context = { environment, project, settings, worker }; - let original = ''; - const file = path.join(__dirname, '../', 'public', 'service-worker.js'); + if (files['service-worker.js']) return files['service-worker.js'] + const sources = [] + const context = { environment, project, settings, worker } + let original = '' + const file = path.join(__dirname, '../', 'public', 'service-worker.js') if (existsSync(file)) { - original = readFileSync(file, 'utf-8'); + original = readFileSync(file, 'utf-8') } const bundleFolder = path.join(__dirname, '../', environment.production ? '.production' : '.development') - const scripts = readdirSync(bundleFolder).filter((filename) => filename.includes('.client.')).map((filename) => `'/${filename}'`) - sources.push(`self.context = ${JSON.stringify(context, null, 2)};`); - sources.push(load); + const scripts = readdirSync(bundleFolder) + .filter((filename) => filename.includes('.client.')) + .map((filename) => `'/${filename}'`) + sources.push(`self.context = ${JSON.stringify(context, null, 2)};`) + sources.push(load) if (environment.mode === 'ssg') { - sources.push(staticHelpers); - sources.push(cacheFirst); - sources.push(staleWhileRevalidate); - sources.push(networkFirst); - sources.push(networkDataFirst); + sources.push(staticHelpers) + sources.push(cacheFirst) + sources.push(staleWhileRevalidate) + sources.push(networkFirst) + sources.push(networkDataFirst) } else { - sources.push(cacheFirst); - sources.push(staleWhileRevalidate); - sources.push(networkFirst); + sources.push(cacheFirst) + sources.push(staleWhileRevalidate) + sources.push(networkFirst) } if (original.indexOf('install') === -1) { - sources.push(environment.mode === 'ssg' ? staticInstall : dynamicInstall); + sources.push(environment.mode === 'ssg' ? staticInstall : dynamicInstall) } if (original.indexOf('activate') === -1) { - sources.push(activate); + sources.push(activate) } if (original.indexOf('fetch') === -1) { - sources.push(environment.mode === 'ssg' ? staticFetch : dynamicFetch); + sources.push(environment.mode === 'ssg' ? staticFetch : dynamicFetch) } if (original) { - sources.push(original); + sources.push(original) } - files['service-worker.js'] = sources.join(`\n\n`).replace(`"{{BUNDLE}}",`, scripts.join(', \n')); - return files['service-worker.js']; + files['service-worker.js'] = sources.join(`\n\n`).replace(`"{{BUNDLE}}",`, scripts.join(', \n')) + return files['service-worker.js'] } -export default worker; \ No newline at end of file +export default worker diff --git a/shared/deserialize.js b/shared/deserialize.js index 4ccde0e1..f59b9496 100644 --- a/shared/deserialize.js +++ b/shared/deserialize.js @@ -1,19 +1,19 @@ -const reISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/; -const reMsAjax = /^\/Date\((d|-|.*)\)[\/|\\]$/; +const reISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/ +const reMsAjax = /^\/Date\((d|-|.*)\)[\/|\\]$/ function dateParser(key, value) { if (typeof value === 'string') { - let a = reISO.exec(value); - if (a) return new Date(value); - a = reMsAjax.exec(value); + let a = reISO.exec(value) + if (a) return new Date(value) + a = reMsAjax.exec(value) if (a) { - const b = a[1].split(/[-+,.]/); - return new Date(b[0] ? +b[0] : 0 - +b[1]); + const b = a[1].split(/[-+,.]/) + return new Date(b[0] ? +b[0] : 0 - +b[1]) } } - return value; -}; + return value +} export default function deserialize(string) { - return JSON.parse(string, dateParser); -} \ No newline at end of file + return JSON.parse(string, dateParser) +} diff --git a/shared/element.js b/shared/element.js index 75ff247a..63f9b08e 100644 --- a/shared/element.js +++ b/shared/element.js @@ -1,4 +1,5 @@ -import fragment from './fragment'; +/* eslint-disable no-undefined */ +import fragment from './fragment' const seed = Object.freeze([]) @@ -9,18 +10,18 @@ function normalize(child) { export default function element(type, props, ...children) { children = seed.concat(...children).map(normalize) if (type === 'textarea') { - children = [children.join('')]; + children = [children.join('')] } - const attributes = { ...props, children }; + const attributes = { ...props, children } if (type === 'style' && !attributes.html) { - attributes.html = children.join(''); + attributes.html = children.join('') } if (type === 'element') { - type = attributes.tag || fragment; - delete attributes.tag; + type = attributes.tag || fragment + delete attributes.tag } if (typeof type === 'function' && type.render !== undefined) { return { type, attributes, children: null } } - return { type, attributes, children }; -} \ No newline at end of file + return { type, attributes, children } +} diff --git a/shared/extractLocation.js b/shared/extractLocation.js index 98f04ae2..f4c0605f 100644 --- a/shared/extractLocation.js +++ b/shared/extractLocation.js @@ -1,19 +1,20 @@ +/* eslint-disable no-undefined */ export default function extractLocation(originalUrl) { - let [target, hash] = originalUrl.split('#'); - let [path, search] = target.split('?'); - if(path !== '/' && path.endsWith('/')) { - path = path.substring(0, path.length - 1); + let [target, hash] = originalUrl.split('#') + let [path, search] = target.split('?') + if (path !== '/' && path.endsWith('/')) { + path = path.substring(0, path.length - 1) } - let url = path; - if(search) { - url += '?' + search; + let url = path + if (search) { + url += `?${search}` } - let urlWithHash = url; - if(hash) { - urlWithHash += '#' + hash; + let urlWithHash = url + if (hash) { + urlWithHash += `#${hash}` } - if(hash === undefined) { - hash = ''; + if (hash === undefined) { + hash = '' } - return {path, search, url, urlWithHash, hash}; -} \ No newline at end of file + return { path, search, url, urlWithHash, hash } +} diff --git a/shared/extractParamValue.js b/shared/extractParamValue.js index b7516b56..07b0b1b8 100644 --- a/shared/extractParamValue.js +++ b/shared/extractParamValue.js @@ -1,5 +1,5 @@ export default function extractParamValue(value) { - if(value === 'true') return true; - if (value === 'false') return false; - return value ? decodeURIComponent(value.replace(/\+/g, ' ')) : ''; -} \ No newline at end of file + if (value === 'true') return true + if (value === 'false') return false + return value ? decodeURIComponent(value.replace(/\+/g, ' ')) : '' +} diff --git a/shared/fragment.js b/shared/fragment.js index 1bbd3748..b94ef408 100644 --- a/shared/fragment.js +++ b/shared/fragment.js @@ -1,3 +1,3 @@ -export default function fragment({children}) { - return children; -} \ No newline at end of file +export default function fragment({ children }) { + return children +} diff --git a/shared/generateKey.js b/shared/generateKey.js index 72eaa113..6e1476cf 100644 --- a/shared/generateKey.js +++ b/shared/generateKey.js @@ -1,8 +1,8 @@ export default function generateKey(scope, node, depth) { - if (node.attributes.key) return node.attributes.key; - const prefix = depth.length === 1 ? 'application' : node.type.name + '/' + depth; + if (node.attributes.key) return node.attributes.key + const prefix = depth.length === 1 ? 'application' : `${node.type.name}/${depth}` if (node.attributes.route) { return prefix + (scope.context.environment.mode === 'ssg' ? scope.context.router.path : scope.context.router.url) } - return prefix; -} \ No newline at end of file + return prefix +} diff --git a/shared/generateTree.js b/shared/generateTree.js index 79ba71dc..ab33a119 100644 --- a/shared/generateTree.js +++ b/shared/generateTree.js @@ -1,18 +1,19 @@ -import generateKey from '../shared/generateKey'; -import { isClass, isFalse, isFunction, isUndefined } from '../shared/nodes'; -import fragment from './fragment'; -import { transformNodes } from './plugins'; +/* eslint-disable no-undefined */ +import generateKey from '../shared/generateKey' +import { isClass, isFalse, isFunction, isUndefined } from '../shared/nodes' +import fragment from './fragment' +import { transformNodes } from './plugins' async function generateBranch(siblings, node, depth, scope) { - - transformNodes(scope, node, depth); + transformNodes(scope, node, depth) if (isUndefined(node)) { let message = 'Attempting to render an undefined node. \n' if (node === undefined) { - message += 'This error usually happens because of a missing return statement around JSX or returning undefined from a renderable function.'; + message += + 'This error usually happens because of a missing return statement around JSX or returning undefined from a renderable function.' } else { - message += 'This error usually happens because of a missing import statement or a typo on a component tag'; + message += 'This error usually happens because of a missing import statement or a typo on a component tag' } throw new Error(message) } @@ -20,78 +21,75 @@ async function generateBranch(siblings, node, depth, scope) { if (isFalse(node)) { siblings.push({ type: false, - attributes: {} - }); - return; + attributes: {}, + }) + return } if (isClass(node)) { const key = generateKey(scope, node, depth) - const instance = scope.instances[key] || new node.type(scope); + const instance = scope.instances[key] || new node.type(scope) instance.persistent = !!node.attributes.persistent - instance.key = key; - instance._attributes = node.attributes; - instance._scope = scope; - let memory; + instance.key = key + instance._attributes = node.attributes + instance._scope = scope + let memory if (scope.memory) { - memory = scope.memory[key]; + memory = scope.memory[key] if (memory) { - instance.prerendered = true; - instance.initiated = true; - Object.assign(instance, memory); - delete scope.memory[key]; + instance.prerendered = true + instance.initiated = true + Object.assign(instance, memory) + delete scope.memory[key] } } - let shouldHydrate = false; - const shouldLaunch = instance.initiated && ( - !instance.prerendered || - (instance.persistent && instance.terminated) - ) + let shouldHydrate = false + const shouldLaunch = instance.initiated && (!instance.prerendered || (instance.persistent && instance.terminated)) if (instance.terminated) { - shouldHydrate = true; - instance.terminated = false; + shouldHydrate = true + instance.terminated = false } - const shouldPrepare = scope.instances[key] === undefined; - scope.instances[key] = instance; + const shouldPrepare = scope.instances[key] === undefined + scope.instances[key] = instance if (shouldPrepare) { if (memory === undefined) { - instance.prepare && instance.prepare(); + instance.prepare && instance.prepare() if (scope.context.environment.server) { - instance.initiate && await instance.initiate(); - instance.initiated = true; - instance.launch && instance.launch(); + instance.initiate && (await instance.initiate()) + instance.initiated = true + instance.launch && instance.launch() } else { - scope.initiationQueue.push(instance); + scope.initiationQueue.push(instance) } } - shouldHydrate = true; + shouldHydrate = true } if (scope.hydrationQueue) { if (shouldHydrate) { - shouldLaunch && instance.launch && instance.launch(); - scope.hydrationQueue.push(instance); + shouldLaunch && instance.launch && instance.launch() + scope.hydrationQueue.push(instance) } else if (instance.initiated === true) { - instance.update && instance.update(); + instance.update && instance.update() } } if (scope.context.environment.client) { - scope.renewalQueue.push(instance); + scope.renewalQueue.push(instance) } - const children = instance.render(); + const children = instance.render() if (children && children.type) { - children.instance = instance; + children.instance = instance } - node.children = [].concat(children); + node.children = [].concat(children) for (let i = 0; i < node.children.length; i++) { - await generateBranch(siblings, node.children[i], depth + '-' + i, scope); + await generateBranch(siblings, node.children[i], `${depth}-${i}`, scope) } - return; + return } if (node.type === 'body') { node.type = fragment for (const attribute in node.attributes) { - if (attribute === 'children' || attribute.startsWith('_')) continue; + if (attribute === 'children' || attribute.startsWith('_')) continue if (attribute === 'class' || attribute === 'style') { if (!scope.nextBody[attribute]) { scope.nextBody[attribute] = [] @@ -114,49 +112,48 @@ async function generateBranch(siblings, node, depth, scope) { } if (isFunction(node)) { - const context = node.type.name ? scope.generateContext(node.attributes) : node.attributes; - const children = node.type(context); - node.children = [].concat(children); + const context = node.type.name ? scope.generateContext(node.attributes) : node.attributes + const children = node.type(context) + node.children = [].concat(children) for (let i = 0; i < node.children.length; i++) { - await generateBranch(siblings, node.children[i], depth + '-' + i, scope); + await generateBranch(siblings, node.children[i], `${depth}-${i}`, scope) } - return; + return } if (node.type) { if (node.type === 'head') { siblings.push({ type: false, - attributes: {} - }); + attributes: {}, + }) for (let i = 0; i < node.children.length; i++) { - const id = depth + '-' + i - await generateBranch(scope.nextHead, node.children[i], id, scope); + const id = `${depth}-${i}` + await generateBranch(scope.nextHead, node.children[i], id, scope) scope.nextHead[scope.nextHead.length - 1].attributes.id ??= id } } else if (node.children) { const branch = { type: node.type, attributes: node.attributes, - children: [] + children: [], } for (let i = 0; i < node.children.length; i++) { - await generateBranch(branch.children, node.children[i], depth + '-' + i, scope); + await generateBranch(branch.children, node.children[i], `${depth}-${i}`, scope) } - siblings.push(branch); + siblings.push(branch) } - return; + return } siblings.push({ type: 'text', - text: node - }); - + text: node, + }) } export default async function generateTree(node, scope) { - const tree = { type: 'div', attributes: { id: 'application' }, children: [] }; - await generateBranch(tree.children, node, '0', scope); - return tree; -} \ No newline at end of file + const tree = { type: 'div', attributes: { id: 'application' }, children: [] } + await generateBranch(tree.children, node, '0', scope) + return tree +} diff --git a/shared/generateTruthyString.js b/shared/generateTruthyString.js index 6978dcdf..d83ea30d 100644 --- a/shared/generateTruthyString.js +++ b/shared/generateTruthyString.js @@ -1,5 +1,8 @@ const seed = Object.freeze([]) export default function generateTruthyString(elements) { - return seed.concat(...elements).filter(Boolean).join(' ') -} \ No newline at end of file + return seed + .concat(...elements) + .filter(Boolean) + .join(' ') +} diff --git a/shared/getProxyableMethods.js b/shared/getProxyableMethods.js index e37fa1e6..2b92688a 100644 --- a/shared/getProxyableMethods.js +++ b/shared/getProxyableMethods.js @@ -1,10 +1,10 @@ export default function getProxyableMethods(target) { - let properties = new Set(); - let current = target; + const properties = new Set() + let current = target do { - Object.getOwnPropertyNames(current).map(name => properties.add(name)) + Object.getOwnPropertyNames(current).map((name) => properties.add(name)) } while ((current = Object.getPrototypeOf(current)) && current != Object.prototype) return [...properties.keys()].filter((name) => { return name !== 'constructor' && typeof target[name] === 'function' - }); -} \ No newline at end of file + }) +} diff --git a/shared/getQueryStringParams.js b/shared/getQueryStringParams.js index 97af0b91..c9af4237 100644 --- a/shared/getQueryStringParams.js +++ b/shared/getQueryStringParams.js @@ -1,14 +1,13 @@ -import extractParamValue from './extractParamValue'; +import extractParamValue from './extractParamValue' export default function getQueryStringParams(url) { - const [path, query] = url.split('?'); - if(query) { + const query = url.split('?')[1] + if (query) { return query.split('&').reduce((params, param) => { - let [key, value] = param.split('='); - params[key] = extractParamValue(value); - return params; - }, {}); - } else { - return {}; + const [key, value] = param.split('=') + params[key] = extractParamValue(value) + return params + }, {}) } -}; \ No newline at end of file + return {} +} diff --git a/shared/nodes.js b/shared/nodes.js index 8644758a..a2c8e2d1 100644 --- a/shared/nodes.js +++ b/shared/nodes.js @@ -1,22 +1,23 @@ +/* eslint-disable no-undefined */ export function isUndefined(node) { if (node === undefined) return true if (node === null) return false - return node.hasOwnProperty('type') && node.type === undefined + return Object.prototype.hasOwnProperty.call(node, 'type') && node.type === undefined } export function isFalse(node) { - if (node === null || node === false) return true; - return node?.hasOwnProperty('type') && node.type === null || node.type === false; + if (node === null || node === false) return true + return (Object.prototype.hasOwnProperty.call(node, 'type') && node.type === null) || node.type === false } export function isClass(node) { - return typeof node.type === 'function' && node.type.prototype && typeof node.type.prototype.render === 'function'; + return typeof node.type === 'function' && node.type.prototype && typeof node.type.prototype.render === 'function' } export function isFunction(node) { - return typeof node.type === 'function'; + return typeof node.type === 'function' } export function isText(node) { return node.type === 'text' -} \ No newline at end of file +} diff --git a/shared/noop.js b/shared/noop.js index 7a9ff1d5..567333d3 100644 --- a/shared/noop.js +++ b/shared/noop.js @@ -1 +1,2 @@ -export default function noop() { } \ No newline at end of file +/* eslint-disable no-empty-function */ +export default function noop() {} diff --git a/shared/plugins.js b/shared/plugins.js index 90c5f0ce..7d9a73e5 100644 --- a/shared/plugins.js +++ b/shared/plugins.js @@ -1,18 +1,13 @@ -import routable from '../plugins/routable'; -import bindable from '../plugins/bindable'; -import parameterizable from '../plugins/parameterizable'; -import anchorable from '../plugins/anchorable'; +import anchorable from '../plugins/anchorable' +import bindable from '../plugins/bindable' +import parameterizable from '../plugins/parameterizable' +import routable from '../plugins/routable' -const plugins = [ - parameterizable, - anchorable, - routable, - bindable -]; +const plugins = [parameterizable, anchorable, routable, bindable] export function transformNodes(scope, node, depth) { for (const plugin of plugins) { - plugin.transform({ ...scope.context, node, depth }); + plugin.transform({ ...scope.context, node, depth }) } } @@ -20,7 +15,7 @@ export function loadPlugins(scope) { for (const plugin of plugins) { plugin.load && plugin.load(scope.context) } - return plugins; + return plugins } export function useClientPlugins(plugin) { @@ -29,4 +24,4 @@ export function useClientPlugins(plugin) { export function useServerPlugins(plugin) { if (plugin.server) plugins.push(plugin) -} \ No newline at end of file +} diff --git a/shared/prefix.js b/shared/prefix.js index 09954843..8443aa8d 100644 --- a/shared/prefix.js +++ b/shared/prefix.js @@ -1,3 +1,3 @@ -const prefix = 'nullstack'; +const prefix = 'nullstack' -export default prefix; \ No newline at end of file +export default prefix diff --git a/shared/routeMatches.js b/shared/routeMatches.js index 0db796a4..b44b886f 100644 --- a/shared/routeMatches.js +++ b/shared/routeMatches.js @@ -1,24 +1,25 @@ -import extractLocation from '../shared/extractLocation'; -import extractParamValue from './extractParamValue'; +/* eslint-disable no-continue */ +import extractLocation from '../shared/extractLocation' +import extractParamValue from './extractParamValue' export default function routeMatches(url, route) { - let {path} = extractLocation(url); - const urlPaths = path.split('/'); - const routePaths = route.split('/'); - const params = {}; - const length = Math.max(urlPaths.length, routePaths.length); - let catchall = false; - for(let i = 0; i < length; i++) { - if(catchall) { - continue; + const { path } = extractLocation(url) + const urlPaths = path.split('/') + const routePaths = route.split('/') + const params = {} + const length = Math.max(urlPaths.length, routePaths.length) + let catchall = false + for (let i = 0; i < length; i++) { + if (catchall) { + continue } else if (routePaths[i] === '*') { - catchall = true; + catchall = true } else if (routePaths[i] && routePaths[i].startsWith(':')) { const key = routePaths[i].replace(':', '') - params[key] = extractParamValue(urlPaths[i]); + params[key] = extractParamValue(urlPaths[i]) } else if (routePaths[i] !== urlPaths[i]) { - return false; + return false } } - return params; -} \ No newline at end of file + return params +} diff --git a/shared/sanitizeString.js b/shared/sanitizeString.js index 1c5695bc..d72f66a2 100644 --- a/shared/sanitizeString.js +++ b/shared/sanitizeString.js @@ -1,9 +1,8 @@ -export function sanitizeHtml(unsafe) { - return unsafe - .replace(//g, '>') -} - -export function sanitizeString(unsafe) { - return unsafe.replace(/<\//g, `<\\\/`) -} \ No newline at end of file +/* eslint-disable no-useless-escape */ +export function sanitizeHtml(unsafe) { + return unsafe.replace(//g, '>') +} + +export function sanitizeString(unsafe) { + return unsafe.replace(/<\//g, `<\\\/`) +} diff --git a/shared/serializeParam.js b/shared/serializeParam.js index 941aa0fc..c770b577 100644 --- a/shared/serializeParam.js +++ b/shared/serializeParam.js @@ -1,3 +1,3 @@ export default function serializeParam(value) { - return value?.toJSON?.() ?? value; -} \ No newline at end of file + return value?.toJSON?.() ?? value +} diff --git a/shared/serializeSearch.js b/shared/serializeSearch.js index 81a51c0c..9a94ddd8 100644 --- a/shared/serializeSearch.js +++ b/shared/serializeSearch.js @@ -1,10 +1,12 @@ export default function serializeSearch(params) { - const keys = Object.keys(params); - return keys.map((key) => { - if(params[key] === false || !!params[key]) { - return `${key}=${params[key]}`; - } else { - return ''; - } - }).filter((segment) => !!segment).join('&'); + const keys = Object.keys(params) + return keys + .map((key) => { + if (params[key] === false || !!params[key]) { + return `${key}=${params[key]}` + } + return '' + }) + .filter((segment) => !!segment) + .join('&') } diff --git a/shared/string.js b/shared/string.js index 598d87e4..2a25a60b 100644 --- a/shared/string.js +++ b/shared/string.js @@ -1,7 +1,7 @@ export function camelize(key) { - return key.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase()); + return key.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase()) } export function kebabize(key) { - return key.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); -}; \ No newline at end of file + return key.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase() +} diff --git a/tests/client.js b/tests/client.js index 8268343f..24b58b0e 100644 --- a/tests/client.js +++ b/tests/client.js @@ -1,14 +1,15 @@ -import Nullstack from 'nullstack'; -import Application from './src/Application'; -import vueable from './src/plugins/vueable'; +import Nullstack from 'nullstack' -Nullstack.use(vueable); +import Application from './src/Application' +import vueable from './src/plugins/vueable' -const context = Nullstack.start(Application); +Nullstack.use(vueable) + +const context = Nullstack.start(Application) context.start = function () { context.startValue = true - setTimeout(() => context.startTimedValue = true, 1000) + setTimeout(() => (context.startTimedValue = true), 1000) } -export default context; \ No newline at end of file +export default context diff --git a/tests/jest-puppeteer.config.js b/tests/jest-puppeteer.config.js index d24779c4..6fa550bb 100644 --- a/tests/jest-puppeteer.config.js +++ b/tests/jest-puppeteer.config.js @@ -5,9 +5,9 @@ const defaultOptions = { server: { command: 'npm run build && node .production/server.js', port: 6969, - launchTimeout: 25000 + launchTimeout: 25000, }, - browserContext: 'incognito' + browserContext: 'incognito', } const ciPipelineOptions = { @@ -19,15 +19,15 @@ const ciPipelineOptions = { '--no-sandbox', '--disable-setuid-sandbox', '--disable-accelerated-2d-canvas', - '--disable-gpu' - ] + '--disable-gpu', + ], }, server: { command: 'npm run build && node .production/server.js', port: 6969, - launchTimeout: 25000 + launchTimeout: 25000, }, - browserContext: 'incognito' + browserContext: 'incognito', } -module.exports = process.env.CI ? ciPipelineOptions : defaultOptions; \ No newline at end of file +module.exports = process.env.CI ? ciPipelineOptions : defaultOptions diff --git a/tests/jest.config.js b/tests/jest.config.js index b6c36829..5a15b3cc 100644 --- a/tests/jest.config.js +++ b/tests/jest.config.js @@ -1,7 +1,7 @@ -const CI = !!process.env.CI; +const CI = !!process.env.CI module.exports = { - preset: "jest-puppeteer", + preset: 'jest-puppeteer', forceExit: CI, - testTimeout: 60000 -} \ No newline at end of file + testTimeout: 60000, +} diff --git a/tests/package.json b/tests/package.json index c73a7a44..e5e8238b 100644 --- a/tests/package.json +++ b/tests/package.json @@ -12,6 +12,7 @@ "puppeteer": "^14.1.1", "purgecss-webpack-plugin": "^4.1.3" }, + "types": "../../types/index.d.ts", "scripts": { "start": "npx nullstack start --input=./tests --port=6969 --env=test --mode=spa", "build": "npx nullstack build --input=./tests --env=test", diff --git a/tests/server.js b/tests/server.js index 08393331..f5465f38 100644 --- a/tests/server.js +++ b/tests/server.js @@ -1,18 +1,19 @@ -import Nullstack from 'nullstack'; -import Application from './src/Application'; -import ContextProject from './src/ContextProject'; -import ContextSecrets from './src/ContextSecrets'; -import ContextSettings from './src/ContextSettings'; -import ContextWorker from './src/ContextWorker'; -import ExposedServerFunctions from './src/ExposedServerFunctions'; -import vueable from './src/plugins/vueable'; -import ServerRequestAndResponse from './src/ServerRequestAndResponse'; +import Nullstack from 'nullstack' -Nullstack.use(vueable); +import Application from './src/Application' +import ContextProject from './src/ContextProject' +import ContextSecrets from './src/ContextSecrets' +import ContextSettings from './src/ContextSettings' +import ContextWorker from './src/ContextWorker' +import ExposedServerFunctions from './src/ExposedServerFunctions' +import vueable from './src/plugins/vueable' +import ServerRequestAndResponse from './src/ServerRequestAndResponse' -const context = Nullstack.start(Application); +Nullstack.use(vueable) -const methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT']; +const context = Nullstack.start(Application) + +const methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT'] context.server.get('/data/all/:param', ExposedServerFunctions.getData) context.server.get('/data/get/:param', ExposedServerFunctions.getData) @@ -26,28 +27,28 @@ context.server.get('/custom-api-before-start', (request, response) => { }) context.server.use('/api', (request, response, next) => { - request.status = 200; + request.status = 200 if (!response.headersSent) { - next(); + next() } -}); +}) for (const method of methods) { context.server[method.toLowerCase()]('/api', (request, response) => { - response.status(request.status).json({ method: request.method }); - }); + response.status(request.status).json({ method: request.method }) + }) } -context.startIncrementalValue = 0; +context.startIncrementalValue = 0 context.start = async function () { - await ContextProject.start(context); - await ContextSecrets.start(context); - await ContextSettings.start(context); - await ContextWorker.start(context); - await ServerRequestAndResponse.start(context); - context.startValue = true; - context.startIncrementalValue++; + await ContextProject.start(context) + await ContextSecrets.start(context) + await ContextSettings.start(context) + await ContextWorker.start(context) + await ServerRequestAndResponse.start(context) + context.startValue = true + context.startIncrementalValue++ } -export default context \ No newline at end of file +export default context diff --git a/tests/src/AnchorModifiers.njs b/tests/src/AnchorModifiers.njs index 449509ae..6b58b710 100644 --- a/tests/src/AnchorModifiers.njs +++ b/tests/src/AnchorModifiers.njs @@ -1,4 +1,4 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' class AnchorModifiers extends Nullstack { @@ -30,7 +30,15 @@ class AnchorModifiers extends Nullstack { render() { return ( -
+
@@ -51,4 +59,4 @@ class AnchorModifiers extends Nullstack { } -export default AnchorModifiers; \ No newline at end of file +export default AnchorModifiers diff --git a/tests/src/AnchorModifiers.test.js b/tests/src/AnchorModifiers.test.js index da255ee4..cc625353 100644 --- a/tests/src/AnchorModifiers.test.js +++ b/tests/src/AnchorModifiers.test.js @@ -1,100 +1,98 @@ describe('AnchorModifiers jsx', () => { - beforeEach(async () => { - await page.goto('http://localhost:6969/anchor-modifiers'); - await page.waitForSelector('[data-hydrated]'); - }); + await page.goto('http://localhost:6969/anchor-modifiers') + await page.waitForSelector('[data-hydrated]') + }) test('Clicking html link with shift opens in new window', async () => { - await page.keyboard.down('Shift'); - await page.click('[href="/anchor-modifiers?source=html"]'); - await page.keyboard.up('Shift'); + await page.keyboard.down('Shift') + await page.click('[href="/anchor-modifiers?source=html"]') + await page.keyboard.up('Shift') const url = await page.url() - expect(url).toEqual('http://localhost:6969/anchor-modifiers'); - }); + expect(url).toEqual('http://localhost:6969/anchor-modifiers') + }) test('Clicking html link with control or meta opens in new tab', async () => { const key = process.platform === 'darwin' ? 'Meta' : 'Control' - await page.keyboard.down(key); - await page.click('[href="/anchor-modifiers?source=html"]'); - await page.keyboard.up(key); + await page.keyboard.down(key) + await page.click('[href="/anchor-modifiers?source=html"]') + await page.keyboard.up(key) const url = await page.url() - expect(url).toEqual('http://localhost:6969/anchor-modifiers'); - }); + expect(url).toEqual('http://localhost:6969/anchor-modifiers') + }) test('Clicking html link with alt downloads the link', async () => { - await page.keyboard.down('Alt'); - await page.click('[href="/anchor-modifiers?source=html"]'); - await page.keyboard.up('Alt'); + await page.keyboard.down('Alt') + await page.click('[href="/anchor-modifiers?source=html"]') + await page.keyboard.up('Alt') const url = await page.url() - expect(url).toEqual('http://localhost:6969/anchor-modifiers'); - }); + expect(url).toEqual('http://localhost:6969/anchor-modifiers') + }) test('Clicking jsx link with shift opens in new window', async () => { - await page.keyboard.down('Shift'); - await page.click('[href="/anchor-modifiers?source=jsx"]'); - await page.keyboard.up('Shift'); + await page.keyboard.down('Shift') + await page.click('[href="/anchor-modifiers?source=jsx"]') + await page.keyboard.up('Shift') const url = await page.url() - expect(url).toEqual('http://localhost:6969/anchor-modifiers'); - }); + expect(url).toEqual('http://localhost:6969/anchor-modifiers') + }) test('Clicking jsx link with control or meta opens in new tab', async () => { const key = process.platform === 'darwin' ? 'Meta' : 'Control' - await page.keyboard.down(key); - await page.click('[href="/anchor-modifiers?source=jsx"]'); - await page.keyboard.up(key); + await page.keyboard.down(key) + await page.click('[href="/anchor-modifiers?source=jsx"]') + await page.keyboard.up(key) const url = await page.url() - expect(url).toEqual('http://localhost:6969/anchor-modifiers'); - }); + expect(url).toEqual('http://localhost:6969/anchor-modifiers') + }) test('Clicking jsx link with alt downloads the link', async () => { - await page.keyboard.down('Alt'); - await page.click('[href="/anchor-modifiers?source=jsx"]'); - await page.keyboard.up('Alt'); + await page.keyboard.down('Alt') + await page.click('[href="/anchor-modifiers?source=jsx"]') + await page.keyboard.up('Alt') const url = await page.url() - expect(url).toEqual('http://localhost:6969/anchor-modifiers'); - }); + expect(url).toEqual('http://localhost:6969/anchor-modifiers') + }) test('Clicking html link with modifier runs the original event', async () => { - await page.keyboard.down('Shift'); - await page.click('[href="/anchor-modifiers?source=html"]'); - await page.keyboard.up('Shift'); - await page.waitForSelector('[data-clicked-html]'); - const element = await page.$('[data-clicked-html]'); - expect(element).toBeTruthy(); - }); + await page.keyboard.down('Shift') + await page.click('[href="/anchor-modifiers?source=html"]') + await page.keyboard.up('Shift') + await page.waitForSelector('[data-clicked-html]') + const element = await page.$('[data-clicked-html]') + expect(element).toBeTruthy() + }) test('Clicking jsx link with modifier runs the original event', async () => { - await page.keyboard.down('Shift'); - await page.click('[href="/anchor-modifiers?source=jsx"]'); - await page.keyboard.up('Shift'); - await page.waitForSelector('[data-clicked-jsx]'); - const element = await page.$('[data-clicked-jsx]'); - expect(element).toBeTruthy(); - }); + await page.keyboard.down('Shift') + await page.click('[href="/anchor-modifiers?source=jsx"]') + await page.keyboard.up('Shift') + await page.waitForSelector('[data-clicked-jsx]') + const element = await page.$('[data-clicked-jsx]') + expect(element).toBeTruthy() + }) test('anchors can have events', async () => { - await page.click('button'); - await page.click('[href="/anchor-modifiers?source=incremented"]'); - await page.waitForSelector('[data-updated] [data-count="1"]'); - const element = await page.$('[data-updated] [data-count="1"]'); - expect(element).toBeTruthy(); - }); + await page.click('button') + await page.click('[href="/anchor-modifiers?source=incremented"]') + await page.waitForSelector('[data-updated] [data-count="1"]') + const element = await page.$('[data-updated] [data-count="1"]') + expect(element).toBeTruthy() + }) test('anchors can have object events', async () => { - await page.click('button'); - await page.click('[href="/anchor-modifiers?source=object"]'); - await page.waitForSelector('[data-updated] [data-objected]'); - const element = await page.$('[data-updated] [data-objected]'); - expect(element).toBeTruthy(); - }); + await page.click('button') + await page.click('[href="/anchor-modifiers?source=object"]') + await page.waitForSelector('[data-updated] [data-objected]') + const element = await page.$('[data-updated] [data-objected]') + expect(element).toBeTruthy() + }) test('anchors can have array events', async () => { - await page.click('button'); - await page.click('[href="/anchor-modifiers?source=array"]'); - await page.waitForSelector('[data-updated] [data-count="1"][data-objected]'); - const element = await page.$('[data-updated] [data-count="1"][data-objected]'); - expect(element).toBeTruthy(); - }); - -}); + await page.click('button') + await page.click('[href="/anchor-modifiers?source=array"]') + await page.waitForSelector('[data-updated] [data-count="1"][data-objected]') + const element = await page.$('[data-updated] [data-count="1"][data-objected]') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/Application.njs b/tests/src/Application.njs index 70930d3f..7c5b0112 100644 --- a/tests/src/Application.njs +++ b/tests/src/Application.njs @@ -1,69 +1,70 @@ -import Nullstack from 'nullstack'; -import AnchorModifiers from './AnchorModifiers'; -import './Application.css'; -import ChildComponent from './ChildComponent'; -import ComponentTernary from './ComponentTernary'; -import Context from './Context'; -import ContextData from './ContextData'; -import ContextEnvironment from './ContextEnvironment'; -import ContextPage from './ContextPage'; -import ContextProject from './ContextProject'; -import ContextSecrets from './ContextSecrets'; -import ContextSettings from './ContextSettings'; -import ContextWorker from './ContextWorker'; -import DateParser from './DateParser'; -import Element from './Element'; -import ErrorOnChildNode from './ErrorOnChildNode'; -import ErrorPage from './ErrorPage'; -import ExternalServerFunctions from './ExternalServerFunctions'; -import FalsyNodes from './FalsyNodes'; -import FullStackLifecycle from './FullStackLifecycle'; -import Instanceable from './Instanceable'; -import InstanceKey from './InstanceKey'; -import InstanceSelf from './InstanceSelf'; -import IsomorphicStartup from './IsomorphicStartup'; -import LazyComponentLoader from './LazyComponentLoader'; -import NestedProxy from './NestedProxy'; -import ParentComponent from './ParentComponent'; -import PersistentComponent from './PersistentComponent'; -import PluginAttributes from './PluginAttributes'; -import PublicServerFunctions from './PublicServerFunctions.njs'; -import PureComponents from './PureComponents'; -import WebpackCustomPlugin from './WebpackCustomPlugin'; -import RenderableComponent from './RenderableComponent'; -import RoutesAndParams from './RoutesAndParams'; -import ServerFunctions from './ServerFunctions'; -import ServerRequestAndResponse from './ServerRequestAndResponse'; -import StatefulComponent from './StatefulComponent'; -import StaticThis from './StaticThis'; -import TwoWayBindings from './TwoWayBindings'; -import TypeScript from './TypeScript'; -import UndefinedNodes from './UndefinedNodes'; -import UnderscoredAttributes from './UnderscoredAttributes'; -import Vunerability from './Vunerability'; -import WindowDependency from './WindowDependency'; -import WorkerVerbs from './WorkerVerbs'; -import MetatagState from './MetatagState'; -import TypeScriptExtension from './TypeScriptExtension'; -import JavaScriptExtension from './JavaScriptExtension'; -import Refs from './Refs'; -import OptimizedEvents from './OptimizedEvents'; +import Nullstack from 'nullstack' + +import AnchorModifiers from './AnchorModifiers' +import './Application.css' +import ArrayAttributes from './ArrayAttributes' +import BodyFragment from './BodyFragment' +import ChildComponent from './ChildComponent' +import ComponentTernary from './ComponentTernary' +import Context from './Context' +import ContextData from './ContextData' +import ContextEnvironment from './ContextEnvironment' +import ContextPage from './ContextPage' +import ContextProject from './ContextProject' +import ContextSecrets from './ContextSecrets' +import ContextSettings from './ContextSettings' +import ContextWorker from './ContextWorker' +import DateParser from './DateParser' import DynamicHead from './DynamicHead' -import TextObserver from './TextObserver'; -import BodyFragment from './BodyFragment'; -import ArrayAttributes from './ArrayAttributes'; -import RouteScroll from './RouteScroll'; -import IsomorphicImport from './IsomorphicImport.njs'; -import ExposedServerFunctions from './ExposedServerFunctions'; +import Element from './Element' +import ErrorOnChildNode from './ErrorOnChildNode' +import ErrorPage from './ErrorPage' +import ExposedServerFunctions from './ExposedServerFunctions' +import ExternalServerFunctions from './ExternalServerFunctions' +import FalsyNodes from './FalsyNodes' +import FullStackLifecycle from './FullStackLifecycle' +import Instanceable from './Instanceable' +import InstanceKey from './InstanceKey' +import InstanceSelf from './InstanceSelf' +import IsomorphicImport from './IsomorphicImport.njs' +import IsomorphicStartup from './IsomorphicStartup' +import JavaScriptExtension from './JavaScriptExtension' +import LazyComponentLoader from './LazyComponentLoader' +import MetatagState from './MetatagState' +import NestedProxy from './NestedProxy' +import OptimizedEvents from './OptimizedEvents' +import ParentComponent from './ParentComponent' +import PersistentComponent from './PersistentComponent' +import PluginAttributes from './PluginAttributes' +import PublicServerFunctions from './PublicServerFunctions.njs' +import PureComponents from './PureComponents' +import Refs from './Refs' +import RenderableComponent from './RenderableComponent' +import RoutesAndParams from './RoutesAndParams' +import RouteScroll from './RouteScroll' +import ServerFunctions from './ServerFunctions' +import ServerRequestAndResponse from './ServerRequestAndResponse' +import StatefulComponent from './StatefulComponent' +import StaticThis from './StaticThis' +import TextObserver from './TextObserver' +import TwoWayBindings from './TwoWayBindings' +import TypeScript from './TypeScript' +import TypeScriptExtension from './TypeScriptExtension' +import UndefinedNodes from './UndefinedNodes' +import UnderscoredAttributes from './UnderscoredAttributes' +import Vunerability from './Vunerability' +import WebpackCustomPlugin from './WebpackCustomPlugin' +import WindowDependency from './WindowDependency' +import WorkerVerbs from './WorkerVerbs' class Application extends Nullstack { async changeInstanceable({ instances }) { - await instances.instanceable.customMethod(); + await instances.instanceable.customMethod() } prepare(context) { - context.string = 'nullstack'; + context.string = 'nullstack' context.refInstanceCount = 0 } @@ -71,7 +72,7 @@ class Application extends Nullstack { return (

{project.name}

- {page.status !== 200 &&
} + {page.status !== 200 &&
}
offline static this @@ -128,7 +129,7 @@ class Application extends Nullstack { - + @@ -144,4 +145,4 @@ class Application extends Nullstack { } -export default Application; \ No newline at end of file +export default Application diff --git a/tests/src/Application.test.js b/tests/src/Application.test.js index a21d4db5..8a4dd3b4 100644 --- a/tests/src/Application.test.js +++ b/tests/src/Application.test.js @@ -1,25 +1,23 @@ describe('Application', () => { - test('the application is running', async () => { - const response = await page.goto('http://localhost:6969'); - const status = response.status(); - expect(status).toBe(200); - }); + const response = await page.goto('http://localhost:6969') + const status = response.status() + expect(status).toBe(200) + }) test('the static start function runs on startup', async () => { - const h1 = await page.$('h1'); - const text = await page.evaluate(element => element.textContent, h1); - expect(text).toMatch('Nullstack Tests'); - }); + const h1 = await page.$('h1') + const text = await page.evaluate((element) => element.textContent, h1) + expect(text).toMatch('Nullstack Tests') + }) test('a stylesheet is generated if css is imported', async () => { - const element = await page.$('[rel="stylesheet"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[rel="stylesheet"]') + expect(element).toBeTruthy() + }) test('simple scripts depending on window should be importable on server', async () => { - const element = await page.$('[data-window="shim"]'); - expect(element).toBeTruthy(); - }); - -}); \ No newline at end of file + const element = await page.$('[data-window="shim"]') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/ArrayAttributes.njs b/tests/src/ArrayAttributes.njs index 104f24d9..9310cb6e 100644 --- a/tests/src/ArrayAttributes.njs +++ b/tests/src/ArrayAttributes.njs @@ -1,4 +1,4 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' class ArrayAttributes extends Nullstack { @@ -20,20 +20,54 @@ class ArrayAttributes extends Nullstack {
{JSON.stringify(this.classes)} {JSON.stringify(this.styles)} - - - - - - - - - - {this.count > 1 && } + + + + + + + + + + {this.count > 1 && ( + + )}
) } } -export default ArrayAttributes; \ No newline at end of file +export default ArrayAttributes diff --git a/tests/src/ArrayAttributes.test.js b/tests/src/ArrayAttributes.test.js index 3067423d..fe772042 100644 --- a/tests/src/ArrayAttributes.test.js +++ b/tests/src/ArrayAttributes.test.js @@ -1,96 +1,94 @@ describe('ArrayAttributes jsx', () => { - beforeEach(async () => { - await page.goto('http://localhost:6969/array-attributes'); + await page.goto('http://localhost:6969/array-attributes') await page.waitForSelector('[data-hydrated]') - }); + }) test('classes can be simple strings', async () => { - await page.click('[data-d]'); + await page.click('[data-d]') await page.waitForSelector('[class="d"]') const element = await page.$('[class="d"]') - expect(element).toBeTruthy(); - }); + expect(element).toBeTruthy() + }) test('when class refers to an array the result is the merge of the strings', async () => { - await page.click('[data-ab]'); + await page.click('[data-ab]') await page.waitForSelector('[class="a b"]') const element = await page.$('[class="a b"]') - expect(element).toBeTruthy(); - }); + expect(element).toBeTruthy() + }) test('when the class array changes the attribute changes', async () => { - await page.click('[data-ab]'); + await page.click('[data-ab]') await page.waitForSelector('[class="a b"]') - await page.click('[data-abc]'); + await page.click('[data-abc]') await page.waitForSelector('[class="a b c"]') const element = await page.$('[class="a b c"]') - expect(element).toBeTruthy(); - }); + expect(element).toBeTruthy() + }) test('falsy values are removed from the class array', async () => { - await page.click('[data-e]'); + await page.click('[data-e]') await page.waitForSelector('[class="e"]') const element = await page.$('[class="e"]') - expect(element).toBeTruthy(); - }); + expect(element).toBeTruthy() + }) test('styles can be simple strings', async () => { - await page.click('[data-purple]'); + await page.click('[data-purple]') await page.waitForSelector('[style="color: purple;"]') const element = await page.$('[style="color: purple;"]') - expect(element).toBeTruthy(); - }); + expect(element).toBeTruthy() + }) test('when style refers to an array the result is the merge of the strings', async () => { - await page.click('[data-pink-blue]'); + await page.click('[data-pink-blue]') await page.waitForSelector('[style="color: pink; background-color: blue;"]') const element = await page.$('[style="color: pink; background-color: blue;"]') - expect(element).toBeTruthy(); - }); + expect(element).toBeTruthy() + }) test('when the style array changes the attribute changes', async () => { - await page.click('[data-pink-blue]'); + await page.click('[data-pink-blue]') await page.waitForSelector('[style="color: pink; background-color: blue;"]') - await page.click('[data-pink-blue-red]'); + await page.click('[data-pink-blue-red]') await page.waitForSelector('[style="color: pink; background-color: blue; border: 1px solid red;"]') const element = await page.$('[style="color: pink; background-color: blue; border: 1px solid red;"]') - expect(element).toBeTruthy(); - }); + expect(element).toBeTruthy() + }) test('falsy values are removed from the style array', async () => { - await page.click('[data-green]'); + await page.click('[data-green]') await page.waitForSelector('[style="color: green;"]') const element = await page.$('[style="color: green;"]') - expect(element).toBeTruthy(); - }); + expect(element).toBeTruthy() + }) test('when events point to an array all the functions are executed in parallel', async () => { - await page.click('[data-events]'); + await page.click('[data-events]') await page.waitForSelector('[data-count="4"]') const element = await page.$('[data-count="4"]') - expect(element).toBeTruthy(); - }); + expect(element).toBeTruthy() + }) test('object events can be mixed with function events in arrays', async () => { - await page.click('[data-events]'); + await page.click('[data-events]') await page.waitForSelector('[data-count="4"][data-objected]') const element = await page.$('[data-count="4"][data-objected]') - expect(element).toBeTruthy(); - }); + expect(element).toBeTruthy() + }) test('elements rendered after hydration can receive array classes', async () => { - await page.click('[data-events]'); + await page.click('[data-events]') await page.waitForSelector('[class="dynamic-a dynamic-b"]') const element = await page.$('[class="dynamic-a dynamic-b"]') - expect(element).toBeTruthy(); - }); + expect(element).toBeTruthy() + }) test('elements rendered after hydration can receive array styles', async () => { - await page.click('[data-events]'); + await page.click('[data-events]') await page.waitForSelector('[class="dynamic-a dynamic-b"][style="color: pink; background-color: blue;"]') const element = await page.$('[class="dynamic-a dynamic-b"][style="color: pink; background-color: blue;"]') - expect(element).toBeTruthy(); - }); - -}); + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/BodyFragment.njs b/tests/src/BodyFragment.njs index c16b2c79..8bc5595a 100644 --- a/tests/src/BodyFragment.njs +++ b/tests/src/BodyFragment.njs @@ -1,4 +1,4 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' class BodyFragment extends Nullstack { @@ -20,15 +20,31 @@ class BodyFragment extends Nullstack { render() { return ( - - + + BodyFragment - {this.visible && + {this.visible && ( BodyFragment2 - } + )} home ) @@ -36,4 +52,4 @@ class BodyFragment extends Nullstack { } -export default BodyFragment; \ No newline at end of file +export default BodyFragment diff --git a/tests/src/BodyFragment.test.js b/tests/src/BodyFragment.test.js index 67e302ca..b95624bf 100644 --- a/tests/src/BodyFragment.test.js +++ b/tests/src/BodyFragment.test.js @@ -1,62 +1,60 @@ beforeEach(async () => { - await page.goto('http://localhost:6969/body-fragment'); -}); + await page.goto('http://localhost:6969/body-fragment') +}) describe('BodyFragment', () => { - test('the body behaves as a fragment and creates no markup', async () => { - const element = await page.$('body > #application > h1'); - expect(element).toBeTruthy(); - }); + const element = await page.$('body > #application > h1') + expect(element).toBeTruthy() + }) test('when the body is nested regular attributes are overwritten by the last one in the tree', async () => { - const element = await page.$('body[data-chars="b"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('body[data-chars="b"]') + expect(element).toBeTruthy() + }) test('when the body is nested classes are merged togheter', async () => { - const element = await page.$('body[class="class-one class-two class-three class-four"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('body[class="class-one class-two class-three class-four"]') + expect(element).toBeTruthy() + }) test('when the body is nested styles are merged togheter', async () => { - const element = await page.$('body[style="background-color: black; color: white;"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('body[style="background-color: black; color: white;"]') + expect(element).toBeTruthy() + }) test('when the body is nested events are invoked sequentially', async () => { await page.waitForSelector('body[data-hydrated]') - await page.click('body'); - await page.waitForSelector('[data-keys][data-objected][data-visible]'); - const element = await page.$('[data-keys][data-objected][data-visible]'); - expect(element).toBeTruthy(); - }); + await page.click('body') + await page.waitForSelector('[data-keys][data-objected][data-visible]') + const element = await page.$('[data-keys][data-objected][data-visible]') + expect(element).toBeTruthy() + }) test('when a body is added to the vdom attributes are added', async () => { await page.waitForSelector('body[data-hydrated]') - await page.click('body'); - await page.waitForSelector('body[data-visible]'); - const element = await page.$('body[data-visible]'); - expect(element).toBeTruthy(); - }); + await page.click('body') + await page.waitForSelector('body[data-visible]') + const element = await page.$('body[data-visible]') + expect(element).toBeTruthy() + }) test('when a body is removed from the vdom attributes are removed', async () => { await page.waitForSelector('body[data-hydrated]') - await page.click('body'); - await page.waitForSelector('body[data-visible]'); - await page.click('body'); - await page.waitForSelector('body:not([data-visible])'); - const element = await page.$('body:not([data-visible])'); - expect(element).toBeTruthy(); - }); + await page.click('body') + await page.waitForSelector('body[data-visible]') + await page.click('body') + await page.waitForSelector('body:not([data-visible])') + const element = await page.$('body:not([data-visible])') + expect(element).toBeTruthy() + }) test('the body removes events when the fragment leaves the tree', async () => { await page.waitForSelector('body[data-hydrated]') - await page.click('[href="/"]'); - await page.waitForSelector('[data-window="shim"]:not([data-count])'); - await page.click('body'); - const element = await page.$('[data-window="shim"]:not([data-count])'); - expect(element).toBeTruthy(); - }); - -}); \ No newline at end of file + await page.click('[href="/"]') + await page.waitForSelector('[data-window="shim"]:not([data-count])') + await page.click('body') + const element = await page.$('[data-window="shim"]:not([data-count])') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/ChildComponent.test.js b/tests/src/ChildComponent.test.js index bc557783..120ab99e 100644 --- a/tests/src/ChildComponent.test.js +++ b/tests/src/ChildComponent.test.js @@ -1,69 +1,67 @@ beforeAll(async () => { - await page.goto('http://localhost:6969/child-component'); -}); + await page.goto('http://localhost:6969/child-component') +}) describe('ChildComponent', () => { - test('Nullstack is auto imported when using inheritance', async () => { - const element = await page.$('[data-current="ChildComponent"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-current="ChildComponent"]') + expect(element).toBeTruthy() + }) test('inner components are overridable', async () => { - const element = await page.$('[data-current]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-current]') + expect(element).toBeTruthy() + }) test('server functions are bound to the class in ssr', async () => { - const element = await page.$('[data-child-this]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-child-this]') + expect(element).toBeTruthy() + }) test('inherited server functions are bound to the class ssr', async () => { - const element = await page.$('[data-parent-this]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-parent-this]') + expect(element).toBeTruthy() + }) test('server functions are bound to the class in spa', async () => { - await page.waitForSelector('[data-hydrated-child-this]'); - const element = await page.$('[data-hydrated-child-this]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-hydrated-child-this]') + const element = await page.$('[data-hydrated-child-this]') + expect(element).toBeTruthy() + }) test('inherited server functions are bound to the class spa', async () => { - await page.waitForSelector('[data-hydrated-parent-this]'); - const element = await page.$('[data-hydrated-parent-this]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-hydrated-parent-this]') + const element = await page.$('[data-hydrated-parent-this]') + expect(element).toBeTruthy() + }) test('static inherited server functions are bound to the original class spa', async () => { - await page.waitForSelector('[data-static-hydrated-parent-this]'); - const element = await page.$('[data-static-hydrated-parent-this]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-static-hydrated-parent-this]') + const element = await page.$('[data-static-hydrated-parent-this]') + expect(element).toBeTruthy() + }) test('static inherited server functions are bound to the original class ssr', async () => { - await page.waitForSelector('[data-static-parent-this]'); - const element = await page.$('[data-static-parent-this]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-static-parent-this]') + const element = await page.$('[data-static-parent-this]') + expect(element).toBeTruthy() + }) test('static server functions are bound to the class in spa', async () => { - await page.waitForSelector('[data-static-hydrated-child-this]'); - const element = await page.$('[data-static-hydrated-child-this]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-static-hydrated-child-this]') + const element = await page.$('[data-static-hydrated-child-this]') + expect(element).toBeTruthy() + }) test('static server functions are bound to the class in srs', async () => { - await page.waitForSelector('[data-static-child-this]'); - const element = await page.$('[data-static-child-this]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-static-child-this]') + const element = await page.$('[data-static-child-this]') + expect(element).toBeTruthy() + }) test('Nullstack is injected in tsx files', async () => { - await page.waitForSelector('[data-static-child-this]'); - const element = await page.$('[data-static-child-this]'); - expect(element).toBeTruthy(); - }); - -}); \ No newline at end of file + await page.waitForSelector('[data-static-child-this]') + const element = await page.$('[data-static-child-this]') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/ChildComponent.tsx b/tests/src/ChildComponent.tsx index 8c212b39..e2a5415a 100644 --- a/tests/src/ChildComponent.tsx +++ b/tests/src/ChildComponent.tsx @@ -1,23 +1,23 @@ -import ParentComponent from './ParentComponent'; +import ParentComponent from './ParentComponent' class ChildComponent extends ParentComponent { static async getChildThis() { - return this.name; + return this.name } async initiate() { - this.parentThis = await this.getParentThis(); - this.childThis = await this.getChildThis(); - this.staticChildThis = await ChildComponent.getChildThis(); - this.staticParentThis = await ParentComponent.getParentThis(); + this.parentThis = await this.getParentThis() + this.childThis = await this.getChildThis() + this.staticChildThis = await ChildComponent.getChildThis() + this.staticParentThis = await ParentComponent.getParentThis() } async hydrate() { - this.hydratedParentThis = await this.getParentThis(); - this.hydratedChildThis = await this.getChildThis(); - this.staticHydratedChildThis = await ChildComponent.getChildThis(); - this.staticHydratedParentThis = await ParentComponent.getParentThis(); + this.hydratedParentThis = await this.getParentThis() + this.hydratedChildThis = await this.getChildThis() + this.staticHydratedChildThis = await ChildComponent.getChildThis() + this.staticHydratedParentThis = await ParentComponent.getParentThis() this.bunda = 'true' } @@ -40,4 +40,4 @@ class ChildComponent extends ParentComponent { } -export default ChildComponent; \ No newline at end of file +export default ChildComponent diff --git a/tests/src/ComponentTernary.njs b/tests/src/ComponentTernary.njs index d59a21f3..5b125324 100644 --- a/tests/src/ComponentTernary.njs +++ b/tests/src/ComponentTernary.njs @@ -1,4 +1,4 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' class CommponentA extends Nullstack { render() { @@ -13,7 +13,6 @@ class CommponentB extends Nullstack { } class ComponentTernary extends Nullstack { - showA = true render() { @@ -27,4 +26,4 @@ class ComponentTernary extends Nullstack { } -export default ComponentTernary; \ No newline at end of file +export default ComponentTernary diff --git a/tests/src/ComponentTernary.test.js b/tests/src/ComponentTernary.test.js index 7fa27e1f..7126d5b6 100644 --- a/tests/src/ComponentTernary.test.js +++ b/tests/src/ComponentTernary.test.js @@ -1,26 +1,24 @@ beforeAll(async () => { - await page.goto('http://localhost:6969/component-ternary'); -}); + await page.goto('http://localhost:6969/component-ternary') +}) describe('ComponentTernary', () => { - test('Component A should be visible by default', async () => { - const element = await page.$('[data-a]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-a]') + expect(element).toBeTruthy() + }) test('Component B should be visible when toggling the ternary', async () => { await page.click('button') - await page.waitForSelector('[data-b]'); - const element = await page.$('[data-b]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-b]') + const element = await page.$('[data-b]') + expect(element).toBeTruthy() + }) test('Component A should be visible when toggling the ternary', async () => { await page.click('button') - await page.waitForSelector('[data-a]'); - const element = await page.$('[data-a]'); - expect(element).toBeTruthy(); - }); - -}); \ No newline at end of file + await page.waitForSelector('[data-a]') + const element = await page.$('[data-a]') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/Context.njs b/tests/src/Context.njs index ca5947a7..a78b5d33 100644 --- a/tests/src/Context.njs +++ b/tests/src/Context.njs @@ -1,15 +1,15 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' class Context extends Nullstack { frameworkInitial = ''; static async setContextKey(context) { - context.framework = 'Nullstack'; + context.framework = 'Nullstack' } static async getContextKey({ framework }) { - return framework; + return framework } static staticFunction(context) { @@ -29,9 +29,9 @@ class Context extends Nullstack { } async initiate(context) { - await this.setContextKey(); - context.framework = await this.getContextKey(); - this.setFrameworkInitial(); + await this.setContextKey() + context.framework = await this.getContextKey() + this.setFrameworkInitial() this.staticFunctionHasNoContext = await Context.staticFunction() this.staticUnderlineFunctionHasNoContext = await Context._staticUnderlineFunction() this.staticAsyncUnderlineFunctionHasNoContext = await Context.invokeStaticAsyncUnderlineFunction() @@ -44,7 +44,7 @@ class Context extends Nullstack { } setFrameworkInitial({ framework }) { - this.frameworkInitial = framework[0]; + this.frameworkInitial = framework[0] } render({ framework }) { @@ -57,11 +57,13 @@ class Context extends Nullstack { data-static-async-underline-function-has-no-context={this.staticAsyncUnderlineFunctionHasNoContext} data-hydrated-static-function-has-no-context={this.hydratedStaticFunctionHasNoContext} data-hydrated-static-underline-function-has-no-context={this.hydratedStaticUnderlineFunctionHasNoContext} - data-hydrated-static-async-underline-function-has-no-context={this.hydratedStaticAsyncUnderlineFunctionHasNoContext} + data-hydrated-static-async-underline-function-has-no-context={ + this.hydratedStaticAsyncUnderlineFunctionHasNoContext + } /> ) } } -export default Context; \ No newline at end of file +export default Context diff --git a/tests/src/Context.test.js b/tests/src/Context.test.js index e7dc3000..68a5515d 100644 --- a/tests/src/Context.test.js +++ b/tests/src/Context.test.js @@ -1,68 +1,66 @@ beforeAll(async () => { - await page.goto('http://localhost:6969/context'); -}); + await page.goto('http://localhost:6969/context') +}) describe('Context', () => { - test('setting a key to the server context makes it permanent', async () => { - const element = await page.$('[data-framework="Nullstack"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-framework="Nullstack"]') + expect(element).toBeTruthy() + }) test('keys of the server context are available to all server functions', async () => { - const element = await page.$('[data-framework="Nullstack"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-framework="Nullstack"]') + expect(element).toBeTruthy() + }) test('setting a key to the client context makes it permanent', async () => { - const element = await page.$('[data-framework="Nullstack"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-framework="Nullstack"]') + expect(element).toBeTruthy() + }) test('keys of the client context are available to all client functions', async () => { - const element = await page.$('[data-framework="Nullstack"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-framework="Nullstack"]') + expect(element).toBeTruthy() + }) test('the client context is merged into all instance methods', async () => { - const element = await page.$('[data-framework-initial="N"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-framework-initial="N"]') + expect(element).toBeTruthy() + }) test('hydrated static async underline function has no context', async () => { - await page.waitForSelector('[data-hydrated-static-async-underline-function-has-no-context]'); - const element = await page.$('[data-hydrated-static-async-underline-function-has-no-context]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-hydrated-static-async-underline-function-has-no-context]') + const element = await page.$('[data-hydrated-static-async-underline-function-has-no-context]') + expect(element).toBeTruthy() + }) test('hydrated static underline function has no context', async () => { - await page.waitForSelector('[data-hydrated-static-underline-function-has-no-context]'); - const element = await page.$('[data-hydrated-static-underline-function-has-no-context]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-hydrated-static-underline-function-has-no-context]') + const element = await page.$('[data-hydrated-static-underline-function-has-no-context]') + expect(element).toBeTruthy() + }) test('hydrated static function has no context', async () => { - await page.waitForSelector('[data-hydrated-static-function-has-no-context]'); - const element = await page.$('[data-hydrated-static-function-has-no-context]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-hydrated-static-function-has-no-context]') + const element = await page.$('[data-hydrated-static-function-has-no-context]') + expect(element).toBeTruthy() + }) test('static async underline function has no context', async () => { - await page.waitForSelector('[data-static-async-underline-function-has-no-context]'); - const element = await page.$('[data-static-async-underline-function-has-no-context]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-static-async-underline-function-has-no-context]') + const element = await page.$('[data-static-async-underline-function-has-no-context]') + expect(element).toBeTruthy() + }) test('static underline function has no context', async () => { - await page.waitForSelector('[data-static-underline-function-has-no-context]'); - const element = await page.$('[data-static-underline-function-has-no-context]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-static-underline-function-has-no-context]') + const element = await page.$('[data-static-underline-function-has-no-context]') + expect(element).toBeTruthy() + }) test('static function has no context', async () => { - await page.waitForSelector('[data-static-function-has-no-context]'); - const element = await page.$('[data-static-function-has-no-context]'); - expect(element).toBeTruthy(); - }); - -}); \ No newline at end of file + await page.waitForSelector('[data-static-function-has-no-context]') + const element = await page.$('[data-static-function-has-no-context]') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/ContextData.njs b/tests/src/ContextData.njs index 5c1b6494..0f4cdb1a 100644 --- a/tests/src/ContextData.njs +++ b/tests/src/ContextData.njs @@ -1,13 +1,13 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' class ContextData extends Nullstack { calculateWithDefault({ data }) { - this.countWithDefault = data.multiplyBy * data.count; + this.countWithDefault = data.multiplyBy * data.count } calculateWithoutDefault({ data }) { - this.countWithoutDefault = data.setTo; + this.countWithoutDefault = data.setTo } render() { @@ -25,4 +25,4 @@ class ContextData extends Nullstack { } -export default ContextData; \ No newline at end of file +export default ContextData diff --git a/tests/src/ContextData.test.js b/tests/src/ContextData.test.js index ff9014b5..f14dd3ed 100644 --- a/tests/src/ContextData.test.js +++ b/tests/src/ContextData.test.js @@ -1,33 +1,31 @@ beforeAll(async () => { - await page.goto('http://localhost:6969/context-data'); -}); + await page.goto('http://localhost:6969/context-data') +}) describe('ContextData', () => { - test('data attributes are added to the dom', async () => { - const element = await page.$('[data-multiply-by="3"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-multiply-by="3"]') + expect(element).toBeTruthy() + }) test('data attributes are merged into the data attribute and passed to events', async () => { - await page.click('[data-multiply-by="3"]'); - await page.waitForSelector('[data-count-with-default="6"]'); - const element = await page.$('[data-count-with-default="6"]'); - expect(element).toBeTruthy(); - }); + await page.click('[data-multiply-by="3"]') + await page.waitForSelector('[data-count-with-default="6"]') + const element = await page.$('[data-count-with-default="6"]') + expect(element).toBeTruthy() + }) test('data attributes are added into a new data object and passed to events if no data attribute is provided', async () => { - await page.click('[data-set-to="2"]'); - await page.waitForSelector('[data-count-without-default="2"]'); - const element = await page.$('[data-count-without-default="2"]'); - expect(element).toBeTruthy(); - }); + await page.click('[data-set-to="2"]') + await page.waitForSelector('[data-count-without-default="2"]') + const element = await page.$('[data-count-without-default="2"]') + expect(element).toBeTruthy() + }) test('data attributes are kebabized when generating the data argument', async () => { - await page.click('[data-set-to="2"]'); - await page.waitForSelector('[data-count-without-default="2"]'); - const element = await page.$('[data-count-without-default="2"]'); - expect(element).toBeTruthy(); - }); - -}); \ No newline at end of file + await page.click('[data-set-to="2"]') + await page.waitForSelector('[data-count-without-default="2"]') + const element = await page.$('[data-count-without-default="2"]') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/ContextEnvironment.njs b/tests/src/ContextEnvironment.njs index 4028aa54..dc04add5 100644 --- a/tests/src/ContextEnvironment.njs +++ b/tests/src/ContextEnvironment.njs @@ -1,4 +1,4 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' class ContextEnvironment extends Nullstack { @@ -19,4 +19,4 @@ class ContextEnvironment extends Nullstack { } -export default ContextEnvironment; \ No newline at end of file +export default ContextEnvironment diff --git a/tests/src/ContextEnvironment.test.js b/tests/src/ContextEnvironment.test.js index c0417a43..414ab056 100644 --- a/tests/src/ContextEnvironment.test.js +++ b/tests/src/ContextEnvironment.test.js @@ -1,49 +1,47 @@ beforeAll(async () => { - await page.goto('http://localhost:6969/context-environment'); -}); + await page.goto('http://localhost:6969/context-environment') +}) describe('ContextEnvironment', () => { - test('is part of the client context', async () => { - const element = await page.$('[data-environment]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-environment]') + expect(element).toBeTruthy() + }) test('has a client key', async () => { - await page.waitForSelector('[data-client="true"]'); - const element = await page.$('[data-client="true"]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-client="true"]') + const element = await page.$('[data-client="true"]') + expect(element).toBeTruthy() + }) test('has a server key', async () => { - await page.waitForSelector('[data-server="false"]'); - const element = await page.$('[data-server="false"]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-server="false"]') + const element = await page.$('[data-server="false"]') + expect(element).toBeTruthy() + }) test('has a development key', async () => { - const element = await page.$('[data-development="false"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-development="false"]') + expect(element).toBeTruthy() + }) test('has a production key', async () => { - const element = await page.$('[data-production="true"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-production="true"]') + expect(element).toBeTruthy() + }) test('has a static key', async () => { - const element = await page.$('[data-static="false"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-static="false"]') + expect(element).toBeTruthy() + }) test('has a key with the environment hash', async () => { - const element = await page.$('[data-key]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-key]') + expect(element).toBeTruthy() + }) test('has a key with the environment name', async () => { - const element = await page.$('[data-name=""]'); - expect(element).toBeTruthy(); - }); - -}); \ No newline at end of file + const element = await page.$('[data-name=""]') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/ContextPage.njs b/tests/src/ContextPage.njs index ce5f491c..dc54321b 100644 --- a/tests/src/ContextPage.njs +++ b/tests/src/ContextPage.njs @@ -1,53 +1,59 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' class ContextPage extends Nullstack { eventTriggered = false; - prepare({page}) { - page.title = 'Nullstack Tests'; - page.image = '/image.jpg'; - page.description = 'Nullstack tests page that tests the context page'; - page.locale = 'pt-BR'; - page.robots = 'index, follow'; + prepare({ page }) { + page.title = 'Nullstack Tests' + page.image = '/image.jpg' + page.description = 'Nullstack tests page that tests the context page' + page.locale = 'pt-BR' + page.robots = 'index, follow' page.schema = { - "@type": "WebSite", - "@id":"#website", - "name":"Nullstack", - "url":"https://nullstack.app" - }; - page.changes = 'weekly'; - page.priority = 1; + '@type': 'WebSite', + '@id': '#website', + name: 'Nullstack', + url: 'https://nullstack.app', + } + page.changes = 'weekly' + page.priority = 1 } - hydrate({page}) { + hydrate({ page }) { window.addEventListener(page.event, () => { - this.eventTriggered = true; - }); + this.eventTriggered = true + }) } - static async raiseStatus({response, status}) { - response.status(401); - return status; + static async raiseStatus({ response, status }) { + response.status(401) + return status } - async requestStatus({status}) { - await this.raiseStatus({status}); + async requestStatus({ status }) { + await this.raiseStatus({ status }) } - render({page}) { + render({ page }) { return (
- - + +
) } } -export default ContextPage; \ No newline at end of file +export default ContextPage diff --git a/tests/src/ContextPage.test.js b/tests/src/ContextPage.test.js index 90d0df98..43d71058 100644 --- a/tests/src/ContextPage.test.js +++ b/tests/src/ContextPage.test.js @@ -1,91 +1,89 @@ beforeAll(async () => { - await page.goto('http://localhost:6969/context-page'); -}); + await page.goto('http://localhost:6969/context-page') +}) describe('ContextPage', () => { - test('is part of the client context', async () => { - const element = await page.$('[data-page]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-page]') + expect(element).toBeTruthy() + }) test('the title key updates the document title', async () => { const text = await page.title() - expect(text).toMatch('Nullstack Tests'); - }); + expect(text).toMatch('Nullstack Tests') + }) test('the title key updates the open graph title', async () => { - const text = await page.$eval('head > meta[property="og:title"]', element => element.content); - expect(text).toMatch('Nullstack Tests'); - }); + const text = await page.$eval('head > meta[property="og:title"]', (element) => element.content) + expect(text).toMatch('Nullstack Tests') + }) test('the title key updates the apple mobile web app title', async () => { - const text = await page.$eval('head > meta[name="apple-mobile-web-app-title"]', element => element.content); - expect(text).toMatch('Nullstack Tests'); - }); + const text = await page.$eval('head > meta[name="apple-mobile-web-app-title"]', (element) => element.content) + expect(text).toMatch('Nullstack Tests') + }) test('the robots key updates the meta robots', async () => { - const text = await page.$eval('head > meta[name="robots"]', element => element.content); - expect(text).toMatch('index, follow'); - }); + const text = await page.$eval('head > meta[name="robots"]', (element) => element.content) + expect(text).toMatch('index, follow') + }) test('the locale key updates the open graph locale', async () => { - const text = await page.$eval('head > meta[property="og:locale"]', element => element.content); - expect(text).toMatch('pt-BR'); - }); + const text = await page.$eval('head > meta[property="og:locale"]', (element) => element.content) + expect(text).toMatch('pt-BR') + }) test('the locale key updates the html lang', async () => { - const text = await page.$eval('html', element => element.lang); - expect(text).toMatch('pt-BR'); - }); + const text = await page.$eval('html', (element) => element.lang) + expect(text).toMatch('pt-BR') + }) test('the image key updates the open graph image with a cdn url', async () => { - const text = await page.$eval('head > meta[property="og:image"]', element => element.content); - expect(text).toMatch('http://localhost:6969/image.jpg'); - }); + const text = await page.$eval('head > meta[property="og:image"]', (element) => element.content) + expect(text).toMatch('http://localhost:6969/image.jpg') + }) test('the description key updates the open graph description', async () => { - const text = await page.$eval('head > meta[property="og:description"]', element => element.content); - expect(text).toMatch('Nullstack tests page that tests the context page'); - }); + const text = await page.$eval('head > meta[property="og:description"]', (element) => element.content) + expect(text).toMatch('Nullstack tests page that tests the context page') + }) test('the description key updates the meta description', async () => { - const text = await page.$eval('head > meta[name="description"]', element => element.content); - expect(text).toMatch('Nullstack tests page that tests the context page'); - }); + const text = await page.$eval('head > meta[name="description"]', (element) => element.content) + expect(text).toMatch('Nullstack tests page that tests the context page') + }) test('the canonical tag is generated if the canonical key is omitted', async () => { - const text = await page.$eval('head > link[rel="canonical"]', element => element.href); - expect(text).toMatch('https://localhost:6969/context-page'); - }); + const text = await page.$eval('head > link[rel="canonical"]', (element) => element.href) + expect(text).toMatch('https://localhost:6969/context-page') + }) test('the schema key generates a json schema', async () => { - const text = await page.$eval('head > script[type="application/ld+json"]', element => element.textContent); - expect(text).toMatch('{"@type":"WebSite","@id":"#website","name":"Nullstack","url":"https://nullstack.app"}'); - }); + const text = await page.$eval('head > script[type="application/ld+json"]', (element) => element.textContent) + expect(text).toMatch('{"@type":"WebSite","@id":"#website","name":"Nullstack","url":"https://nullstack.app"}') + }) test('has a changes key', async () => { - const element = await page.$('[data-changes="weekly"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-changes="weekly"]') + expect(element).toBeTruthy() + }) test('has a priority key', async () => { - const element = await page.$('[data-priority="1"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-priority="1"]') + expect(element).toBeTruthy() + }) test('a custom event is triggered when the title changes', async () => { - await page.click('button'); - await page.waitForSelector('[data-event-triggered]'); - const element = await page.$('[data-event-triggered]'); - expect(element).toBeTruthy(); - }); + await page.click('button') + await page.waitForSelector('[data-event-triggered]') + const element = await page.$('[data-event-triggered]') + expect(element).toBeTruthy() + }) test('the page reacts to a server function status', async () => { - await page.click('[status="401"]'); - await page.waitForSelector('[data-page-status="401"]'); - const element = await page.$('[data-page-status="401"]'); - expect(element).toBeTruthy(); - }); - -}); \ No newline at end of file + await page.click('[status="401"]') + await page.waitForSelector('[data-page-status="401"]') + const element = await page.$('[data-page-status="401"]') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/ContextProject.njs b/tests/src/ContextProject.njs index e45fe8ff..1e5c9acc 100644 --- a/tests/src/ContextProject.njs +++ b/tests/src/ContextProject.njs @@ -1,23 +1,23 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' class ContextProject extends Nullstack { static async start({ project }) { - project.color = '#d22365'; - project.backgroundColor = '#d22365'; - project.type = 'website'; - project.display = 'standalone'; - project.orientation = 'portrait'; - project.scope = '/'; - project.root = '/'; + project.color = '#d22365' + project.backgroundColor = '#d22365' + project.type = 'website' + project.display = 'standalone' + project.orientation = 'portrait' + project.scope = '/' + project.root = '/' project.icons = { - '72': '/icon-72x72.png', - '144': '/icon-144x144.png' - }; - project.favicon = '/favicon-96x96.png'; - project.disallow = ['/admin']; - project.sitemap = true; - project.viewport = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no viewport-fit=cover'; + 72: '/icon-72x72.png', + 144: '/icon-144x144.png', + } + project.favicon = '/favicon-96x96.png' + project.disallow = ['/admin'] + project.sitemap = true + project.viewport = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no viewport-fit=cover' } render({ project }) { @@ -47,4 +47,4 @@ class ContextProject extends Nullstack { } -export default ContextProject; \ No newline at end of file +export default ContextProject diff --git a/tests/src/ContextProject.test.js b/tests/src/ContextProject.test.js index 26d0ab55..5f048221 100644 --- a/tests/src/ContextProject.test.js +++ b/tests/src/ContextProject.test.js @@ -1,164 +1,160 @@ describe('ContextProject', () => { - test('cors can be enabled', async () => { - const response = await page.goto('http://localhost:6969/context-project'); - const status = response.status(); - expect([200, 304]).toContain(status); - }); - -}); + const response = await page.goto('http://localhost:6969/context-project') + const status = response.status() + expect([200, 304]).toContain(status) + }) +}) describe('ContextProject', () => { - beforeAll(async () => { - await page.goto('http://localhost:6969/context-project', { waitUntil: "networkidle0" }); - await page.waitForSelector('[data-project]'); - }); + await page.goto('http://localhost:6969/context-project', { waitUntil: 'networkidle0' }) + await page.waitForSelector('[data-project]') + }) test('project is part of the context', async () => { - const element = await page.$('[data-project]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-project]') + expect(element).toBeTruthy() + }) test('has a name key', async () => { - const element = await page.$('[data-name="Nullstack Tests"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-name="Nullstack Tests"]') + expect(element).toBeTruthy() + }) test('has a shortName key', async () => { - const element = await page.$('[data-short-name="Nullstack"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-short-name="Nullstack"]') + expect(element).toBeTruthy() + }) test('has a domain key', async () => { - const element = await page.$('[data-domain="localhost"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-domain="localhost"]') + expect(element).toBeTruthy() + }) test('has a color key', async () => { - const element = await page.$('[data-color="#d22365"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-color="#d22365"]') + expect(element).toBeTruthy() + }) test('has a backgroundColor key', async () => { - const element = await page.$('[data-background-color="#d22365"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-background-color="#d22365"]') + expect(element).toBeTruthy() + }) test('has a type key', async () => { - const element = await page.$('[data-type="website"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-type="website"]') + expect(element).toBeTruthy() + }) test('has a display key', async () => { - const element = await page.$('[data-display="standalone"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-display="standalone"]') + expect(element).toBeTruthy() + }) test('has an orientation key', async () => { - const element = await page.$('[data-orientation="portrait"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-orientation="portrait"]') + expect(element).toBeTruthy() + }) test('has a scope key', async () => { - const element = await page.$('[data-scope="/"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-scope="/"]') + expect(element).toBeTruthy() + }) test('has a root key', async () => { - const element = await page.$('[data-root="/"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-root="/"]') + expect(element).toBeTruthy() + }) test('has an icons key', async () => { - const element = await page.$('[data-icons]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-icons]') + expect(element).toBeTruthy() + }) test('icons accepts an object with sizes', async () => { - const element = await page.$('[data-icon-72="/icon-72x72.png"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-icon-72="/icon-72x72.png"]') + expect(element).toBeTruthy() + }) test('has a favicon key', async () => { - const element = await page.$('[data-favicon="/favicon-96x96.png"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-favicon="/favicon-96x96.png"]') + expect(element).toBeTruthy() + }) test('has a disallow key', async () => { - const element = await page.$('[data-disallow]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-disallow]') + expect(element).toBeTruthy() + }) test('disallow accepts an array with paths', async () => { - const element = await page.$('[data-disallow-admin="/admin"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-disallow-admin="/admin"]') + expect(element).toBeTruthy() + }) test('has a sitemap key', async () => { - const element = await page.$('[data-sitemap]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-sitemap]') + expect(element).toBeTruthy() + }) test('has a viewport key', async () => { - const element = await page.$('[data-viewport="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no viewport-fit=cover"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$( + '[data-viewport="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no viewport-fit=cover"]', + ) + expect(element).toBeTruthy() + }) test('css bundle can use a cdn', async () => { - const text = await page.$eval('[rel="stylesheet"]', (element) => element.href); - expect(text).toMatch('localhost:6969'); - }); + const text = await page.$eval('[rel="stylesheet"]', (element) => element.href) + expect(text).toMatch('localhost:6969') + }) test('javacript bundle can use a cdn', async () => { - const text = await page.$eval('script[integrity]', (element) => element.src); - expect(text).toMatch('localhost:6969'); - }); + const text = await page.$eval('script[integrity]', (element) => element.src) + expect(text).toMatch('localhost:6969') + }) test('css bundle has an empty integrity in dev mode', async () => { - const text = await page.$eval('[rel="stylesheet"]', (element) => element.integrity); - expect(text).toMatch(''); - }); + const text = await page.$eval('[rel="stylesheet"]', (element) => element.integrity) + expect(text).toMatch('') + }) test('javacript bundle has an empty integrity in dev mode', async () => { - const text = await page.$eval('script[integrity]', (element) => element.integrity); - expect(text).toMatch(''); - }); + const text = await page.$eval('script[integrity]', (element) => element.integrity) + expect(text).toMatch('') + }) test('manifest has an empty integrity in dev mode', async () => { - const text = await page.$eval('[rel="manifest"]', (element) => element.integrity); - expect(text).toMatch(''); - }); - -}); + const text = await page.$eval('[rel="manifest"]', (element) => element.integrity) + expect(text).toMatch('') + }) +}) describe('robots.txt', () => { - - let text; + let text beforeAll(async () => { - await page.goto('http://localhost:6969/robots.txt', { waitUntil: "networkidle0" }); - text = await page.evaluate(() => document.body.innerHTML); - }); + await page.goto('http://localhost:6969/robots.txt', { waitUntil: 'networkidle0' }) + text = await page.evaluate(() => document.body.innerHTML) + }) test('it generates a robot.txt', async () => { const index = text.indexOf('User-Agent: *') - expect(index).toBeGreaterThan(-1); - }); + expect(index).toBeGreaterThan(-1) + }) test('it allows project.root', async () => { const index = text.indexOf('Allow: /') - expect(index).toBeGreaterThan(-1); - }); + expect(index).toBeGreaterThan(-1) + }) test('it reflects project.disallow', async () => { const index = text.indexOf('Disallow: /admin') - expect(index).toBeGreaterThan(-1); - }); + expect(index).toBeGreaterThan(-1) + }) test('it reflects project.sitemap', async () => { const index = text.indexOf('Sitemap: https://localhost:6969/sitemap.xml') - expect(index).toBeGreaterThan(-1); - }); - -}); \ No newline at end of file + expect(index).toBeGreaterThan(-1) + }) +}) diff --git a/tests/src/ContextSecrets.njs b/tests/src/ContextSecrets.njs index d9fde2e0..2d8c3d70 100644 --- a/tests/src/ContextSecrets.njs +++ b/tests/src/ContextSecrets.njs @@ -1,19 +1,19 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' class ContextSecrets extends Nullstack { secrets = {}; static async start({ secrets }) { - secrets.anyEnvironment = 'secrets'; + secrets.anyEnvironment = 'secrets' } static async leakSecrets({ secrets }) { - return secrets; + return secrets } async initiate() { - this.secrets = await this.leakSecrets(); + this.secrets = await this.leakSecrets() } render({ secrets }) { @@ -29,4 +29,4 @@ class ContextSecrets extends Nullstack { } -export default ContextSecrets; \ No newline at end of file +export default ContextSecrets diff --git a/tests/src/ContextSecrets.test.js b/tests/src/ContextSecrets.test.js index 78606163..bf5c0140 100644 --- a/tests/src/ContextSecrets.test.js +++ b/tests/src/ContextSecrets.test.js @@ -1,27 +1,25 @@ beforeAll(async () => { - await page.goto('http://localhost:6969/context-secrets'); -}); + await page.goto('http://localhost:6969/context-secrets') +}) describe('ContextSecrets', () => { - test('secrets are not exposed to the client context', async () => { - const element = await page.$('[data-secrets]'); - expect(element).toBeFalsy(); - }); + const element = await page.$('[data-secrets]') + expect(element).toBeFalsy() + }) test('keys starting with NULLSTACK_SECRETS_ are read from the environment', async () => { - const element = await page.$('[data-key="secrets"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-key="secrets"]') + expect(element).toBeTruthy() + }) test('environment keys are camelized', async () => { - const element = await page.$('[data-camelized-key="secrets"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-camelized-key="secrets"]') + expect(element).toBeTruthy() + }) test('keys assigned to secrets stay in the context of development and production environments', async () => { - const element = await page.$('[data-any-environment="secrets"]'); - expect(element).toBeTruthy(); - }); - -}); \ No newline at end of file + const element = await page.$('[data-any-environment="secrets"]') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/ContextSettings.njs b/tests/src/ContextSettings.njs index 6aa03f82..acbf2d87 100644 --- a/tests/src/ContextSettings.njs +++ b/tests/src/ContextSettings.njs @@ -1,11 +1,11 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' class ContextSettings extends Nullstack { settings = {}; static async start({ settings }) { - settings.anyEnvironment = 'settings'; + settings.anyEnvironment = 'settings' } render({ settings }) { @@ -21,4 +21,4 @@ class ContextSettings extends Nullstack { } -export default ContextSettings; \ No newline at end of file +export default ContextSettings diff --git a/tests/src/ContextSettings.test.js b/tests/src/ContextSettings.test.js index f34146fa..4e7f49d8 100644 --- a/tests/src/ContextSettings.test.js +++ b/tests/src/ContextSettings.test.js @@ -1,27 +1,25 @@ beforeAll(async () => { - await page.goto('http://localhost:6969/context-settings'); -}); + await page.goto('http://localhost:6969/context-settings') +}) describe('ContextSettings', () => { - test('settings are exposed to the client context', async () => { - const element = await page.$('[data-settings]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-settings]') + expect(element).toBeTruthy() + }) test('keys starting with NULLSTACK_SETTINGS_ are read from the environment', async () => { - const element = await page.$('[data-key="settings"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-key="settings"]') + expect(element).toBeTruthy() + }) test('environment keys are camelized', async () => { - const element = await page.$('[data-camelized-key="settings"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-camelized-key="settings"]') + expect(element).toBeTruthy() + }) test('keys assigned to settings stay in the context of development and production environments', async () => { - const element = await page.$('[data-any-environment="settings"]'); - expect(element).toBeTruthy(); - }); - -}); \ No newline at end of file + const element = await page.$('[data-any-environment="settings"]') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/ContextWorker.njs b/tests/src/ContextWorker.njs index 76f09512..364cedb0 100644 --- a/tests/src/ContextWorker.njs +++ b/tests/src/ContextWorker.njs @@ -1,7 +1,7 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)) } class ContextWorker extends Nullstack { @@ -9,33 +9,33 @@ class ContextWorker extends Nullstack { header = ''; static async serverFunctionName() { - await sleep(1000); + await sleep(1000) } async invokeServerFunction() { - await this.serverFunctionName(); + await this.serverFunctionName() } static async start({ worker }) { - worker.enabled = true; - worker.preload = ['/context-worker']; + worker.enabled = true + worker.preload = ['/context-worker'] } static async inspectHeaders({ request }) { - return request.headers.custom; + return request.headers.custom } async hydrate({ worker }) { - worker.headers.custom = 'custom'; - this.header = await this.inspectHeaders(); + worker.headers.custom = 'custom' + this.header = await this.inspectHeaders() } static async longServerFunction({ id }) { - await sleep(3000); + await sleep(3000) } invokeServerFunction({ worker, id }) { - this.longServerFunction({ id }); + this.longServerFunction({ id }) this.didFetch = worker.fetching } @@ -53,19 +53,19 @@ class ContextWorker extends Nullstack {
-
id)?.join(',')}>
- - - {worker.registration && -
- } - {worker.installation && -
- } +
id)?.join(',')} /> + + + {worker.registration &&
} + {worker.installation &&
}
) } } -export default ContextWorker; \ No newline at end of file +export default ContextWorker diff --git a/tests/src/ContextWorker.test.js b/tests/src/ContextWorker.test.js index bd21572f..2874dd40 100644 --- a/tests/src/ContextWorker.test.js +++ b/tests/src/ContextWorker.test.js @@ -1,65 +1,64 @@ describe('ContextWorker', () => { - beforeAll(async () => { - await page.goto('http://localhost:6969/context-worker'); - }); + await page.goto('http://localhost:6969/context-worker') + }) test('queues keys always return at least an empty array', async () => { - const element = await page.$('[data-queues-length="0"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-queues-length="0"]') + expect(element).toBeTruthy() + }) test('fetching is set to false when the worker is idle', async () => { - const element = await page.$('[data-fetching]'); - expect(element).toBeFalsy(); - }); + const element = await page.$('[data-fetching]') + expect(element).toBeFalsy() + }) test('has a cdn key', async () => { - const element = await page.$('[data-cdn="http://localhost:6969"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-cdn="http://localhost:6969"]') + expect(element).toBeTruthy() + }) test('is part of the client context', async () => { - const element = await page.$('[data-worker]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-worker]') + expect(element).toBeTruthy() + }) test('has an enabled key', async () => { - const element = await page.$('[data-enabled]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-enabled]') + expect(element).toBeTruthy() + }) test('has an online key', async () => { - const element = await page.$('[data-online]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-online]') + expect(element).toBeTruthy() + }) test('has an responsive key', async () => { - const element = await page.$('[data-responsive]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-responsive]') + expect(element).toBeTruthy() + }) test('has an preload key', async () => { - const element = await page.$('[data-preload]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-preload]') + expect(element).toBeTruthy() + }) test('preload accepts an array of paths', async () => { - const element = await page.$('[data-preload-path="/context-worker"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-preload-path="/context-worker"]') + expect(element).toBeTruthy() + }) test('service worker registration is available as the registration key', async () => { - await page.waitForSelector('[data-registration="ServiceWorkerRegistration"]'); - const element = await page.$('[data-registration="ServiceWorkerRegistration"]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-registration="ServiceWorkerRegistration"]') + const element = await page.$('[data-registration="ServiceWorkerRegistration"]') + expect(element).toBeTruthy() + }) test('headers can be customized', async () => { - await page.waitForSelector('[data-header="custom"]'); - const element = await page.$('[data-header="custom"]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-header="custom"]') + const element = await page.$('[data-header="custom"]') + expect(element).toBeTruthy() + }) /* TODO - beforeinstallationprompt is not firing in puppeteer test('installation prompt is available as the installation key', async () => { @@ -68,54 +67,49 @@ describe('ContextWorker', () => { expect(element).toBeTruthy(); }); */ - -}); +}) describe('ContextWorker', () => { - beforeAll(async () => { - await page.goto('http://localhost:6969/context-worker', { waitUntil: "networkidle0" }); - }); + await page.goto('http://localhost:6969/context-worker', { waitUntil: 'networkidle0' }) + }) test('fetching is set to true when the worker is fetching', async () => { - await page.waitForTimeout(1000); - await page.click('button'); - await page.waitForSelector('[data-fetching]'); - const element = await page.$('[data-fetching]'); - expect(element).toBeTruthy(); - }); - -}); + await page.waitForTimeout(1000) + await page.click('button') + await page.waitForSelector('[data-fetching]') + const element = await page.$('[data-fetching]') + expect(element).toBeTruthy() + }) +}) describe('ContextWorker', () => { - beforeAll(async () => { - await page.goto('http://localhost:6969/context-worker', { waitUntil: "networkidle0" }); - }); + await page.goto('http://localhost:6969/context-worker', { waitUntil: 'networkidle0' }) + }) test('fetching is set to the arguments of the server function when the worker is fetching', async () => { - await page.click('#a'); - await page.waitForSelector('[data-queues="a"]'); - let element = await page.$('[data-queues="a"]'); - expect(element).toBeTruthy(); - - element = await page.$('[data-queues-length="1"]'); - expect(element).toBeTruthy(); - - await page.click('#b'); - await page.waitForSelector('[data-queues="a,b"]'); - element = await page.$('[data-queues="a,b"]'); - expect(element).toBeTruthy(); - - element = await page.$('[data-queues-length="2"]'); - expect(element).toBeTruthy(); - - await page.waitForTimeout(4000); - element = await page.$('[data-queues="a,b"]'); - expect(element).toBeFalsy(); - - element = await page.$('[data-queues-length="0"]'); - expect(element).toBeTruthy(); - }); - -}); \ No newline at end of file + await page.click('#a') + await page.waitForSelector('[data-queues="a"]') + let element = await page.$('[data-queues="a"]') + expect(element).toBeTruthy() + + element = await page.$('[data-queues-length="1"]') + expect(element).toBeTruthy() + + await page.click('#b') + await page.waitForSelector('[data-queues="a,b"]') + element = await page.$('[data-queues="a,b"]') + expect(element).toBeTruthy() + + element = await page.$('[data-queues-length="2"]') + expect(element).toBeTruthy() + + await page.waitForTimeout(4000) + element = await page.$('[data-queues="a,b"]') + expect(element).toBeFalsy() + + element = await page.$('[data-queues-length="0"]') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/DateParser.njs b/tests/src/DateParser.njs index 70e6c737..67902637 100644 --- a/tests/src/DateParser.njs +++ b/tests/src/DateParser.njs @@ -1,13 +1,13 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' class DateParser extends Nullstack { object = null; prepare(context) { - const date = new Date('1992-10-16'); + const date = new Date('1992-10-16') context.object = { date } - this.object = { date }; + this.object = { date } } render({ object }) { @@ -23,4 +23,4 @@ class DateParser extends Nullstack { } -export default DateParser; \ No newline at end of file +export default DateParser diff --git a/tests/src/DateParser.test.js b/tests/src/DateParser.test.js index 7ac05541..9962362e 100644 --- a/tests/src/DateParser.test.js +++ b/tests/src/DateParser.test.js @@ -1,29 +1,27 @@ beforeAll(async () => { - await page.goto('http://localhost:6969/date-parser'); -}); + await page.goto('http://localhost:6969/date-parser') +}) describe('DateParser', () => { - test('dates in the instance are instances of Date in ssr mode', async () => { - const element = await page.$('[data-ssr-instance-year="92"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-ssr-instance-year="92"]') + expect(element).toBeTruthy() + }) test('dates in the context are instances of Date in ssr mode', async () => { - const element = await page.$('[data-ssr-context-year="92"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-ssr-context-year="92"]') + expect(element).toBeTruthy() + }) test('dates in the instance are instances of Date in spa mode', async () => { - await page.waitForSelector('[data-spa-instance-year="92"]'); - const element = await page.$('[data-spa-instance-year="92"]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-spa-instance-year="92"]') + const element = await page.$('[data-spa-instance-year="92"]') + expect(element).toBeTruthy() + }) test('dates in the context are instances of Date in spa mode', async () => { - await page.waitForSelector('[data-spa-context-year="92"]'); - const element = await page.$('[data-spa-context-year="92"]'); - expect(element).toBeTruthy(); - }); - -}); \ No newline at end of file + await page.waitForSelector('[data-spa-context-year="92"]') + const element = await page.$('[data-spa-context-year="92"]') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/DynamicHead.njs b/tests/src/DynamicHead.njs index 9705f975..a41cb9b8 100644 --- a/tests/src/DynamicHead.njs +++ b/tests/src/DynamicHead.njs @@ -1,4 +1,4 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' class DynamicHead extends Nullstack { @@ -7,9 +7,7 @@ class DynamicHead extends Nullstack { renderHead() { const innerComponent = `[data-inner-component] { color: blue }` - return ( - - {this.hydrated && -
- } + {this.hydrated &&
} @@ -53,20 +51,34 @@ class StatefulComponent extends Nullstack { =1

{this.empty}

- + <>
- - {this.visible && } - + + {this.visible && ( + + )} + ) } } -export default StatefulComponent; \ No newline at end of file +export default StatefulComponent diff --git a/tests/src/StatefulComponent.test.js b/tests/src/StatefulComponent.test.js index ab390015..fcac5557 100644 --- a/tests/src/StatefulComponent.test.js +++ b/tests/src/StatefulComponent.test.js @@ -1,98 +1,96 @@ beforeAll(async () => { - await page.goto('http://localhost:6969/stateful-component'); -}); + await page.goto('http://localhost:6969/stateful-component') +}) describe('StatefulComponent', () => { - test('state is being reflected in the dom', async () => { - const element = await page.$('[data-count="1"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-count="1"]') + expect(element).toBeTruthy() + }) test('state is being reflected in the dom', async () => { - const element = await page.$('[data-object-count="0"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-object-count="0"]') + expect(element).toBeTruthy() + }) test('date instance variables are being hydrated as dates', async () => { - const element = await page.$('[data-year="1992"]'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-year="1992"]') + expect(element).toBeTruthy() + }) test('state is being updated by events', async () => { - await page.click('.increment-by-one'); - await page.waitForSelector('[data-count="2"]'); - const element = await page.$('[data-count="2"]'); - expect(element).toBeTruthy(); - }); + await page.click('.increment-by-one') + await page.waitForSelector('[data-count="2"]') + const element = await page.$('[data-count="2"]') + expect(element).toBeTruthy() + }) test('event attributes are merged into the function context', async () => { - await page.click('.increment-by-two'); - await page.waitForSelector('[data-count="4"]'); - const element = await page.$('[data-count="4"]'); - expect(element).toBeTruthy(); - }); + await page.click('.increment-by-two') + await page.waitForSelector('[data-count="4"]') + const element = await page.$('[data-count="4"]') + expect(element).toBeTruthy() + }) test('objects can be passed to events', async () => { - await page.click('.set-to-one'); - await page.waitForSelector('[data-count="1"]'); - const element = await page.$('[data-count="1"]'); - expect(element).toBeTruthy(); - }); + await page.click('.set-to-one') + await page.waitForSelector('[data-count="1"]') + const element = await page.$('[data-count="1"]') + expect(element).toBeTruthy() + }) test('object events can declare a source', async () => { - await page.click('.set-object-to-one'); - await page.waitForSelector('[data-object-count="1"]'); - const element = await page.$('[data-object-count="1"]'); - expect(element).toBeTruthy(); - }); + await page.click('.set-object-to-one') + await page.waitForSelector('[data-object-count="1"]') + const element = await page.$('[data-object-count="1"]') + expect(element).toBeTruthy() + }) test('empty strings generate nodes', async () => { - await page.click('[data-fill]'); - await page.waitForSelector('[data-empty="not"]'); - const text = await page.$eval('[data-empty="not"]', (e) => e.textContent); - expect(text).toMatch('not'); - }); + await page.click('[data-fill]') + await page.waitForSelector('[data-empty="not"]') + const text = await page.$eval('[data-empty="not"]', (e) => e.textContent) + expect(text).toMatch('not') + }) test('rendered attributes undefined values do not raise errors', async () => { - await page.click('[data-toggle]'); - await page.waitForSelector('[data-undefined-event]'); - await page.click('[data-undefined-event]'); + await page.click('[data-toggle]') + await page.waitForSelector('[data-undefined-event]') + await page.click('[data-undefined-event]') let hasConsoleError = false - page.on("console", () => hasConsoleError = true) + page.on('console', () => (hasConsoleError = true)) await page.waitForTimeout(2000) - expect(hasConsoleError).toBeFalsy(); - }); + expect(hasConsoleError).toBeFalsy() + }) test('textareas with multiple nodes become a single node', async () => { - const text = await page.$eval('textarea', (e) => e.value); - expect(text).toMatch(' 1 1 '); - }); + const text = await page.$eval('textarea', (e) => e.value) + expect(text).toMatch(' 1 1 ') + }) test('textareas with multiple nodes can be updated', async () => { - await page.click('.increment-by-one'); - await page.waitForSelector('[data-count="2"]'); - const text = await page.$eval('textarea', (e) => e.value); - expect(text).toMatch(' 2 2 '); - }); + await page.click('.increment-by-one') + await page.waitForSelector('[data-count="2"]') + const text = await page.$eval('textarea', (e) => e.value) + expect(text).toMatch(' 2 2 ') + }) test('children of style become the tags html attribute', async () => { - await page.click('.increment-by-one'); - await page.waitForSelector('[data-count="2"]'); - const text = await page.$eval('button', (e) => getComputedStyle(e).backgroundColor); - expect(text).toMatch('rgba(0, 0, 0, 0.2)'); - }); + await page.click('.increment-by-one') + await page.waitForSelector('[data-count="2"]') + const text = await page.$eval('button', (e) => getComputedStyle(e).backgroundColor) + expect(text).toMatch('rgba(0, 0, 0, 0.2)') + }) test('attributes can prerender a zero', async () => { - await page.waitForSelector('[data-zero="0"]'); - const element = await page.$('[data-zero="0"]'); - expect(element).toBeTruthy(); - }); + await page.waitForSelector('[data-zero="0"]') + const element = await page.$('[data-zero="0"]') + expect(element).toBeTruthy() + }) test('attributes can rererender a zero', async () => { - await page.waitForSelector('[data-hydrated-zero="0"]'); - const element = await page.$('[data-hydrated-zero="0"]'); - expect(element).toBeTruthy(); - }); - -}); \ No newline at end of file + await page.waitForSelector('[data-hydrated-zero="0"]') + const element = await page.$('[data-hydrated-zero="0"]') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/StaticThis.njs b/tests/src/StaticThis.njs index c6efcdc0..c7e7c828 100644 --- a/tests/src/StaticThis.njs +++ b/tests/src/StaticThis.njs @@ -1,23 +1,21 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' class StaticThis extends Nullstack { - name = ''; + name = '' static async getThisName() { - return this.name; + return this.name } async initiate() { - this.name = await this.getThisName(); + this.name = await this.getThisName() } - + render() { - return ( -
- ) + return
} } -export default StaticThis; \ No newline at end of file +export default StaticThis diff --git a/tests/src/StaticThis.test.js b/tests/src/StaticThis.test.js index 33b7f124..5683e05e 100644 --- a/tests/src/StaticThis.test.js +++ b/tests/src/StaticThis.test.js @@ -1,27 +1,23 @@ describe('StaticThis ssr', () => { - beforeAll(async () => { - await page.goto('http://localhost:6969/static-this'); - }); + await page.goto('http://localhost:6969/static-this') + }) test('this is bound to the class in server functions', async () => { - const element = await page.$('[data-name]'); - expect(element).toBeTruthy(); - }); - -}); + const element = await page.$('[data-name]') + expect(element).toBeTruthy() + }) +}) describe('StaticThis spa', () => { - beforeAll(async () => { - await page.goto('http://localhost:6969/'); - await page.click('[href="/static-this"]'); - }); + await page.goto('http://localhost:6969/') + await page.click('[href="/static-this"]') + }) test('this is bound to the class in server functions', async () => { - await page.waitForSelector('[data-name]'); - const element = await page.$('[data-name]'); - expect(element).toBeTruthy(); - }); - -}); \ No newline at end of file + await page.waitForSelector('[data-name]') + const element = await page.$('[data-name]') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/TextObserver.njs b/tests/src/TextObserver.njs index 91f8e061..94e6f395 100644 --- a/tests/src/TextObserver.njs +++ b/tests/src/TextObserver.njs @@ -1,4 +1,4 @@ -import Nullstack from 'nullstack'; +import Nullstack from 'nullstack' class TextObserver extends Nullstack { @@ -10,15 +10,15 @@ class TextObserver extends Nullstack { characterData: true, childList: true, subtree: true, - }; + } const observer = new MutationObserver((mutationsList, observer) => { for (const mutation of mutationsList) { mutation.target.parentElement.dataset.mutated = true } - observer.disconnect(); - }); + observer.disconnect() + }) for (const element of [...document.querySelectorAll('[data-text-observer]')]) { - observer.observe(element, config); + observer.observe(element, config) } this.mutatedText = 'mutated' } @@ -26,13 +26,22 @@ class TextObserver extends Nullstack { render() { return (
- regular text - {this.unmutatedText} - {this.mutatedText} + + {' '} + regular text{' '} + + + {' '} + {this.unmutatedText}{' '} + + + {' '} + {this.mutatedText}{' '} +
) } } -export default TextObserver; \ No newline at end of file +export default TextObserver diff --git a/tests/src/TextObserver.test.js b/tests/src/TextObserver.test.js index e2f0f31f..07c365d4 100644 --- a/tests/src/TextObserver.test.js +++ b/tests/src/TextObserver.test.js @@ -1,23 +1,21 @@ beforeAll(async () => { - await page.goto('http://localhost:6969/text-observer'); + await page.goto('http://localhost:6969/text-observer') await page.waitForSelector('[data-mutated]') -}); +}) describe('TextObserver', () => { - test('regular text should never be mutated', async () => { - const element = await page.$('[data-regular-text]:not([data-mutated])'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-regular-text]:not([data-mutated])') + expect(element).toBeTruthy() + }) test('unmutated text should not be mutated', async () => { - const element = await page.$('[data-unmutated-text]:not([data-mutated])'); - expect(element).toBeTruthy(); - }); + const element = await page.$('[data-unmutated-text]:not([data-mutated])') + expect(element).toBeTruthy() + }) test('mutated text should be mutated and redundancy should be redundant', async () => { - const element = await page.$('[data-mutated-text][data-mutated]'); - expect(element).toBeTruthy(); - }); - -}); \ No newline at end of file + const element = await page.$('[data-mutated-text][data-mutated]') + expect(element).toBeTruthy() + }) +}) diff --git a/tests/src/TwoWayBindings.njs b/tests/src/TwoWayBindings.njs index 589408bb..8a2b4f38 100644 --- a/tests/src/TwoWayBindings.njs +++ b/tests/src/TwoWayBindings.njs @@ -1,16 +1,17 @@ -import Nullstack from 'nullstack'; -import TwoWayBindingsExternalComponent from './TwoWayBindingsExternalComponent'; +import Nullstack from 'nullstack' + +import TwoWayBindingsExternalComponent from './TwoWayBindingsExternalComponent' class TwoWayBindings extends Nullstack { number = 1; - currency = 100; - boolean = true; - character = 'a'; - text = 'aaaa'; + currency = 100 + boolean = true + character = 'a' + text = 'aaaa' - object = { count: 1 }; - array = ['a', 'b', 'c']; + object = { count: 1 } + array = ['a', 'b', 'c'] byKeyName = 'byKeyNameValue' keyName = 'byKeyName' @@ -27,28 +28,26 @@ class TwoWayBindings extends Nullstack { debounceTime = 1000 parse({ event, source: bind, callback }) { - const normalized = event.target.value.replace(',', '').padStart(3, '0'); - const whole = (parseInt(normalized.slice(0, -2)) || 0).toString(); - const decimal = normalized.slice(normalized.length - 2); - const value = parseFloat(whole + '.' + decimal); - const bringsHappiness = value >= 1000000; + const normalized = event.target.value.replace(',', '').padStart(3, '0') + const whole = (parseInt(normalized.slice(0, -2)) || 0).toString() + const decimal = normalized.slice(normalized.length - 2) + const value = parseFloat(`${whole}.${decimal}`) + const bringsHappiness = value >= 1000000 bind.object[bind.property] = value callback({ bringsHappiness }) } renderCurrencyInput({ bind, onchange }) { - const formatted = bind.object[bind.property].toFixed(2).replace('.', ','); + const formatted = bind.object[bind.property].toFixed(2).replace('.', ',') return } renderBubble({ bind }) { - return ( - - ) + return } updateCharacter({ value }) { - this.character = this.array[value]; + this.character = this.array[value] } setHappiness({ bringsHappiness }) { @@ -70,24 +69,32 @@ class TwoWayBindings extends Nullstack { {this.number > 1 &&
} ) diff --git a/tests/src/TextObserver.njs b/tests/src/TextObserver.njs index 94e6f395..e0a97036 100644 --- a/tests/src/TextObserver.njs +++ b/tests/src/TextObserver.njs @@ -11,11 +11,11 @@ class TextObserver extends Nullstack { childList: true, subtree: true, } - const observer = new MutationObserver((mutationsList, observer) => { + const observer = new MutationObserver((mutationsList, current) => { for (const mutation of mutationsList) { mutation.target.parentElement.dataset.mutated = true } - observer.disconnect() + current.disconnect() }) for (const element of [...document.querySelectorAll('[data-text-observer]')]) { observer.observe(element, config) @@ -27,16 +27,13 @@ class TextObserver extends Nullstack { return (
- {' '} - regular text{' '} + regular text - {' '} - {this.unmutatedText}{' '} + {this.unmutatedText} - {' '} - {this.mutatedText}{' '} + {this.mutatedText}
) diff --git a/tests/src/TypeScript.nts b/tests/src/TypeScript.nts index 1e1a9fa4..f57b65a4 100644 --- a/tests/src/TypeScript.nts +++ b/tests/src/TypeScript.nts @@ -1,5 +1,5 @@ import Nullstack from 'nullstack' -import { NullstackClientContext } from 'nullstack/types' +import { NullstackClientContext } from 'nullstack/types/index.d.ts' interface TypeScriptClientContext extends NullstackClientContext { value: number; diff --git a/tests/src/UndefinedNodes.njs b/tests/src/UndefinedNodes.njs index 1c86972d..ac78cbfc 100644 --- a/tests/src/UndefinedNodes.njs +++ b/tests/src/UndefinedNodes.njs @@ -1,10 +1,10 @@ +/* eslint-disable nullstack/prettier */ /* eslint-disable nullstack/no-undef */ import Nullstack from 'nullstack' class UndefinedNodes extends Nullstack { renderWithoutReturn() { - // eslint-disable-next-line nullstack/prettier
forgot to return
} diff --git a/tests/src/plugins/vueable.js b/tests/src/plugins/vueable.js index 72c3c84f..edd6d1d2 100644 --- a/tests/src/plugins/vueable.js +++ b/tests/src/plugins/vueable.js @@ -1,4 +1,3 @@ -/* eslint-disable no-undefined */ function match(node) { return ( node && diff --git a/tests/src/scripts/run.js b/tests/src/scripts/run.js index bf468b62..a4e68859 100644 --- a/tests/src/scripts/run.js +++ b/tests/src/scripts/run.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ process.env.NULLSTACK_ENVIRONMENT_NAME = 'test' const { default: application } = require('../../.production/server.js') diff --git a/types/ClientContext.d.ts b/types/ClientContext.d.ts index 1cc44ab8..f72ab737 100644 --- a/types/ClientContext.d.ts +++ b/types/ClientContext.d.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { NullstackEnvironment } from './Environment.d.ts' import { NullstackNode } from './JSX.d.ts' import { NullstackPage } from './Page.d.ts' diff --git a/types/JSX.d.ts b/types/JSX.d.ts index c06b3db7..06316274 100644 --- a/types/JSX.d.ts +++ b/types/JSX.d.ts @@ -1,7 +1,3 @@ -/* eslint-disable @typescript-eslint/ban-types */ -/* eslint-disable nullstack/no-unused-vars */ -/* eslint-disable @typescript-eslint/no-empty-interface */ -/* eslint-disable @typescript-eslint/no-explicit-any */ // Type definitions for Nullstack // Project: http://github.com/nullstack/nullstack/ // Definitions by: Asana @@ -73,7 +69,7 @@ export interface ClassAttributes extends Attributes { type DetailedHTMLFactory = P -export interface SVGFactory {} +export interface SVGFactory { } export type NullstackFragment = NullstackNode[] export type NullstackNode = NullstackFragment | string | number | boolean | null | undefined @@ -200,8 +196,8 @@ export interface WheelEvent extends MouseEvent type EventHandler> = | object | { - bivarianceHack(event: { event: E } & NullstackClientContext): void - }['bivarianceHack'] + bivarianceHack(event: { event: E } & NullstackClientContext): void + }['bivarianceHack'] type NullstackEventHandler = EventHandler> type DragEventHandler = EventHandler> @@ -220,7 +216,7 @@ type WheelEventHandler = EventHandler> type DetailedHTMLProps, T> = E -export interface SVGProps extends SVGAttributes, ClassAttributes {} +export interface SVGProps extends SVGAttributes, ClassAttributes { } export interface DOMAttributes extends Attributes { // Focus Events @@ -451,17 +447,17 @@ export interface AriaAttributes { * @see aria-atomic. */ 'aria-relevant'?: - | 'additions' - | 'additions removals' - | 'additions text' - | 'all' - | 'removals' - | 'removals additions' - | 'removals text' - | 'text' - | 'text additions' - | 'text removals' - | undefined + | 'additions' + | 'additions removals' + | 'additions text' + | 'all' + | 'removals' + | 'removals additions' + | 'removals text' + | 'text' + | 'text additions' + | 'text removals' + | undefined /** Indicates that user input is required on the element before a form may be submitted. */ 'aria-required'?: Booleanish /** Defines a human-readable, author-localized description for the role of an element. */ @@ -1427,6 +1423,6 @@ declare global { element: ElementTagHTMLAttributes } - interface IntrinsicElements extends ExoticElements, AllElements {} + interface IntrinsicElements extends ExoticElements, AllElements { } } } diff --git a/types/Page.d.ts b/types/Page.d.ts index b36a1e3c..c78e5db6 100644 --- a/types/Page.d.ts +++ b/types/Page.d.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ export type NullstackPage = { /** * Current page title diff --git a/types/ServerContext.d.ts b/types/ServerContext.d.ts index a8787980..3184da6b 100644 --- a/types/ServerContext.d.ts +++ b/types/ServerContext.d.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { NullstackEnvironment } from './Environment.d.ts' import { NullstackProject } from './Project.d.ts' import { NullstackSecrets } from './Secrets.d.ts' diff --git a/types/Worker.d.ts b/types/Worker.d.ts index 47ab199a..33159633 100644 --- a/types/Worker.d.ts +++ b/types/Worker.d.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ export type NullstackWorker = { /** * - keys: server functions names diff --git a/webpack.config.js b/webpack.config.js index 453c077f..edb5ad22 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,3 @@ -/* eslint-disable no-empty-function */ const CopyPlugin = require('copy-webpack-plugin') const crypto = require('crypto') const { existsSync, readdirSync } = require('fs') diff --git a/workers/cacheFirst.js b/workers/cacheFirst.js index ab93645c..23d15573 100644 --- a/workers/cacheFirst.js +++ b/workers/cacheFirst.js @@ -1,5 +1,3 @@ -/* eslint-disable nullstack/no-undef */ -/* eslint-disable nullstack/no-unused-vars */ async function cacheFirst(event) { const cache = await caches.open(self.context.environment.key) const cachedResponse = await cache.match(event.request) diff --git a/workers/dynamicFetch.js b/workers/dynamicFetch.js index d020c4f3..e7357c79 100644 --- a/workers/dynamicFetch.js +++ b/workers/dynamicFetch.js @@ -1,4 +1,3 @@ -/* eslint-disable nullstack/no-undef */ function dynamicStrategy(event) { event.waitUntil( (async function () { diff --git a/workers/load.js b/workers/load.js index e825719e..69851149 100644 --- a/workers/load.js +++ b/workers/load.js @@ -1,4 +1,3 @@ -/* eslint-disable nullstack/no-unused-vars */ async function load(event) { const response = await event.preloadResponse if (response) return response diff --git a/workers/networkDataFirst.js b/workers/networkDataFirst.js index 34a3b1a4..511cb518 100644 --- a/workers/networkDataFirst.js +++ b/workers/networkDataFirst.js @@ -1,5 +1,3 @@ -/* eslint-disable nullstack/no-unused-vars */ -/* eslint-disable nullstack/no-undef */ async function networkDataFirst(event) { const cache = await caches.open(self.context.environment.key) const url = new URL(event.request.url) diff --git a/workers/networkFirst.js b/workers/networkFirst.js index f0acb0f5..39eb649b 100644 --- a/workers/networkFirst.js +++ b/workers/networkFirst.js @@ -1,5 +1,3 @@ -/* eslint-disable nullstack/no-undef */ -/* eslint-disable nullstack/no-unused-vars */ async function networkFirst(event) { const cache = await caches.open(self.context.environment.key) try { diff --git a/workers/staleWhileRevalidate.js b/workers/staleWhileRevalidate.js index 851d3c65..6afc1eef 100644 --- a/workers/staleWhileRevalidate.js +++ b/workers/staleWhileRevalidate.js @@ -1,5 +1,3 @@ -/* eslint-disable nullstack/no-undef */ -/* eslint-disable nullstack/no-unused-vars */ async function staleWhileRevalidate(event) { const cache = await caches.open(self.context.environment.key) const cachedResponse = await cache.match(event.request) diff --git a/workers/staticFetch.js b/workers/staticFetch.js index 8b7ba980..c8f5a5e1 100644 --- a/workers/staticFetch.js +++ b/workers/staticFetch.js @@ -1,4 +1,3 @@ -/* eslint-disable nullstack/no-undef */ function staticStrategy(event) { event.waitUntil( (async function () { diff --git a/workers/staticHelpers.js b/workers/staticHelpers.js index ac79d6ae..9614bcb2 100644 --- a/workers/staticHelpers.js +++ b/workers/staticHelpers.js @@ -1,4 +1,3 @@ -/* eslint-disable nullstack/no-unused-vars */ function withAPI(url) { const fragments = url.split('?') let path = fragments[0] diff --git a/workers/staticInstall.js b/workers/staticInstall.js index 6dbeba23..7c128077 100644 --- a/workers/staticInstall.js +++ b/workers/staticInstall.js @@ -1,4 +1,3 @@ -/* eslint-disable nullstack/no-undef */ function install(event) { const urls = [ '/', From 4cda05f6f88378008b2faedceb51f025bf7864c0 Mon Sep 17 00:00:00 2001 From: Christian Mortaro Date: Thu, 1 Dec 2022 23:56:43 -0300 Subject: [PATCH 6/9] :construction: update linter --- .eslintrc | 7 +------ client/events.js | 1 + client/instanceProxyHandler.js | 5 ++--- client/objectProxyHandler.js | 5 ++--- client/page.js | 5 ++--- client/render.js | 1 + package.json | 4 ++-- server/client.js | 5 ++--- server/context.js | 5 ++--- server/instanceProxyHandler.js | 5 ++--- tests/package.json | 5 ++++- tests/src/Application.njs | 2 +- tests/src/ComponentTernary.njs | 1 + tests/src/Element.njs | 1 + tests/src/ErrorOnChildNode.njs | 1 + tests/src/NestedProxy.njs | 1 + tests/src/RouteScroll.njs | 1 + tests/src/ScriptRun.test.js | 4 +++- tests/src/StatefulComponent.test.js | 4 ++-- tests/src/TypeScriptExtension.tsx | 3 +-- 20 files changed, 33 insertions(+), 33 deletions(-) diff --git a/.eslintrc b/.eslintrc index 24cfb430..301f78e8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -25,12 +25,7 @@ "browser": "writable" }, "rules": { - "no-undefined": "off", - "no-console": "off", - "no-empty-function": "off", - "no-continue": "off", - "no-inner-declarations": "off", - "no-explicit-any": "off", + "no-console": "off" }, "ignorePatterns": [ "temp.js", diff --git a/client/events.js b/client/events.js index 2027fb56..23732e82 100644 --- a/client/events.js +++ b/client/events.js @@ -8,6 +8,7 @@ export const eventDebouncer = new WeakMap() function executeEvent(callback, subject, event, data) { if (typeof callback === 'object') { + console.log(subject, callback) Object.assign(subject.source, callback) } else { callback({ ...subject, event, data }) diff --git a/client/instanceProxyHandler.js b/client/instanceProxyHandler.js index 9a9e1d5f..6c68ac68 100644 --- a/client/instanceProxyHandler.js +++ b/client/instanceProxyHandler.js @@ -1,4 +1,3 @@ -/* eslint-disable prefer-rest-params */ import client from './client' import { generateContext } from './context' import { generateObjectProxy } from './objectProxyHandler' @@ -6,7 +5,7 @@ import { generateObjectProxy } from './objectProxyHandler' export const instanceProxies = new WeakMap() const instanceProxyHandler = { - get(target, name) { + get(target, name, receiver) { if (name === '_isProxy') return true if (target.constructor[name]?.name === '_invoke') return target.constructor[name].bind(target.constructor) if (typeof target[name] === 'function' && name !== 'constructor') { @@ -22,7 +21,7 @@ const instanceProxyHandler = { } return named } - return Reflect.get(...arguments) + return Reflect.get(target, name, receiver) }, set(target, name, value) { if (!name.startsWith('_')) { diff --git a/client/objectProxyHandler.js b/client/objectProxyHandler.js index 06b27d06..c269bfb7 100644 --- a/client/objectProxyHandler.js +++ b/client/objectProxyHandler.js @@ -1,4 +1,3 @@ -/* eslint-disable prefer-rest-params */ import client from './client' const objectProxyHandler = { @@ -13,9 +12,9 @@ const objectProxyHandler = { } return true }, - get(target, name) { + get(target, name, receiver) { if (name === '_isProxy') return true - return Reflect.get(...arguments) + return Reflect.get(target, name, receiver) }, } diff --git a/client/page.js b/client/page.js index e9ad4c51..c0ae045b 100644 --- a/client/page.js +++ b/client/page.js @@ -1,4 +1,3 @@ -/* eslint-disable prefer-rest-params */ import client from './client' import state from './state' import windowEvent from './windowEvent' @@ -11,11 +10,11 @@ const page = { delete state.page const pageProxyHandler = { - set(target, name, value) { + set(target, name, value, receiver) { if (name === 'title') { document.title = value } - const result = Reflect.set(...arguments) + const result = Reflect.set(target, name, value, receiver) if (name === 'title') { windowEvent('page') } diff --git a/client/render.js b/client/render.js index e4b6276d..b85ff893 100644 --- a/client/render.js +++ b/client/render.js @@ -35,6 +35,7 @@ export default function render(node, options) { const eventName = name.substring(2) const callback = generateCallback(node.element, name) node.element.addEventListener(eventName, callback) + console.log(node.attributes.onclick) eventCallbacks.set(node.element, callback) eventSubjects.set(node.element, node.attributes) } diff --git a/package.json b/package.json index 24cb8d6d..06d6b0f8 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "css-loader": "6.7.1", "dotenv": "8.6.0", "eslint-plugin-jest": "^27.1.6", - "eslint-plugin-nullstack": "^0.0.1", + "eslint-plugin-nullstack": "^0.0.4", "express": "4.18.1", "fs-extra": "10.1.0", "mini-css-extract-plugin": "2.6.0", @@ -47,4 +47,4 @@ "webpack-dev-server": "4.9.0", "ws": "7.5.7" } -} +} \ No newline at end of file diff --git a/server/client.js b/server/client.js index b93578cc..fd5eaa56 100644 --- a/server/client.js +++ b/server/client.js @@ -1,9 +1,8 @@ -/* eslint-disable prefer-rest-params */ export function generateContext(context) { const contextProxyHandler = { - set(target, name, value) { + set(target, name, value, receiver) { context[name] = value - return Reflect.set(...arguments) + return Reflect.set(target, name, value, receiver) }, get(target, name) { return target[name] === undefined ? context[name] : target[name] diff --git a/server/context.js b/server/context.js index 9a76e45c..e3b87d4c 100644 --- a/server/context.js +++ b/server/context.js @@ -1,10 +1,9 @@ -/* eslint-disable prefer-rest-params */ const context = {} const contextProxyHandler = { - set(target, name, value) { + set(target, name, value, receiver) { context[name] = value - return Reflect.set(...arguments) + return Reflect.set(target, name, value, receiver) }, } diff --git a/server/instanceProxyHandler.js b/server/instanceProxyHandler.js index ef7e794a..fb481413 100644 --- a/server/instanceProxyHandler.js +++ b/server/instanceProxyHandler.js @@ -1,6 +1,5 @@ -/* eslint-disable prefer-rest-params */ const instanceProxyHandler = { - get(target, name) { + get(target, name, receiver) { if (typeof target[name] === 'function' && name !== 'constructor') { if (name.startsWith('_')) { return target[name].bind(target) @@ -10,7 +9,7 @@ const instanceProxyHandler = { return target[name](context) } } - return Reflect.get(...arguments) + return Reflect.get(target, name, receiver) }, } diff --git a/tests/package.json b/tests/package.json index e5e8238b..8da74831 100644 --- a/tests/package.json +++ b/tests/package.json @@ -18,5 +18,8 @@ "build": "npx nullstack build --input=./tests --env=test", "test": "npm run build && jest --runInBand", "script": "node src/scripts/run.js" + }, + "dependencies": { + "eslint-plugin-nullstack": "^0.0.3" } -} \ No newline at end of file +} diff --git a/tests/src/Application.njs b/tests/src/Application.njs index 2cf63d42..25ff8241 100644 --- a/tests/src/Application.njs +++ b/tests/src/Application.njs @@ -113,8 +113,8 @@ class Application extends Nullstack { - + diff --git a/tests/src/ComponentTernary.njs b/tests/src/ComponentTernary.njs index 3f6e8527..302fec4a 100644 --- a/tests/src/ComponentTernary.njs +++ b/tests/src/ComponentTernary.njs @@ -28,6 +28,7 @@ class ComponentTernary extends Nullstack {
) } + } export default ComponentTernary diff --git a/tests/src/Element.njs b/tests/src/Element.njs index e2e52098..15d72b74 100644 --- a/tests/src/Element.njs +++ b/tests/src/Element.njs @@ -35,6 +35,7 @@ class Element extends Nullstack { ) } + } export default Element diff --git a/tests/src/ErrorOnChildNode.njs b/tests/src/ErrorOnChildNode.njs index 8d25a251..0350cb4b 100644 --- a/tests/src/ErrorOnChildNode.njs +++ b/tests/src/ErrorOnChildNode.njs @@ -72,6 +72,7 @@ class ErrorOnChildNode extends Nullstack { ) } + } export default ErrorOnChildNode diff --git a/tests/src/NestedProxy.njs b/tests/src/NestedProxy.njs index 2ca4be55..9d75b702 100644 --- a/tests/src/NestedProxy.njs +++ b/tests/src/NestedProxy.njs @@ -51,6 +51,7 @@ class NestedProxy extends Nullstack { /> ) } + } export default NestedProxy diff --git a/tests/src/RouteScroll.njs b/tests/src/RouteScroll.njs index 8e1cb003..b56939bd 100644 --- a/tests/src/RouteScroll.njs +++ b/tests/src/RouteScroll.njs @@ -33,6 +33,7 @@ class RouteScroll extends Nullstack {
) } + } export default RouteScroll diff --git a/tests/src/ScriptRun.test.js b/tests/src/ScriptRun.test.js index 02fb3b4b..cf12828e 100644 --- a/tests/src/ScriptRun.test.js +++ b/tests/src/ScriptRun.test.js @@ -1,6 +1,8 @@ -const exec = promisify(require('child_process').exec) +const childProcess = require('child_process') const { promisify } = require('util') +const exec = promisify(childProcess.exec) + test('built packages can run scripts', async () => { const { stdout } = await exec('npm run script') const line = stdout.split('\n').find((l) => l === 'Nullstack Tests') diff --git a/tests/src/StatefulComponent.test.js b/tests/src/StatefulComponent.test.js index fcac5557..5d6d53f3 100644 --- a/tests/src/StatefulComponent.test.js +++ b/tests/src/StatefulComponent.test.js @@ -65,14 +65,14 @@ describe('StatefulComponent', () => { test('textareas with multiple nodes become a single node', async () => { const text = await page.$eval('textarea', (e) => e.value) - expect(text).toMatch(' 1 1 ') + expect(text).toMatch('1 1') }) test('textareas with multiple nodes can be updated', async () => { await page.click('.increment-by-one') await page.waitForSelector('[data-count="2"]') const text = await page.$eval('textarea', (e) => e.value) - expect(text).toMatch(' 2 2 ') + expect(text).toMatch('2 2') }) test('children of style become the tags html attribute', async () => { diff --git a/tests/src/TypeScriptExtension.tsx b/tests/src/TypeScriptExtension.tsx index d9f2c463..16cc8870 100644 --- a/tests/src/TypeScriptExtension.tsx +++ b/tests/src/TypeScriptExtension.tsx @@ -1,5 +1,4 @@ -import Nullstack from 'nullstack' -import { NullstackClientContext } from 'nullstack/types' +import Nullstack, { NullstackClientContext } from 'nullstack' interface TypeScriptExtensionProps { generic: boolean From bdc8192838b319ad434ca99e60a4b0edf551ff85 Mon Sep 17 00:00:00 2001 From: Christian Mortaro Date: Fri, 2 Dec 2022 19:05:46 -0300 Subject: [PATCH 7/9] :construction: fixing source loader --- .eslintrc | 28 ++++++++++----------- .prettierrc | 7 ------ builders/spa.js | 14 +++++------ builders/ssg.js | 18 +++++++------- builders/ssr.js | 4 +-- client/events.js | 1 - client/render.js | 1 - client/worker.js | 2 +- loaders/add-source-to-node.js | 47 ++++++++++++++++++++++++++++------- package.json | 2 +- scripts/index.js | 12 ++++----- server/printError.js | 14 +++++------ server/server.js | 4 +-- shared/deserialize.js | 2 +- tests/src/scripts/run.js | 2 +- 15 files changed, 88 insertions(+), 70 deletions(-) delete mode 100644 .prettierrc diff --git a/.eslintrc b/.eslintrc index 301f78e8..d1154df3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,27 +6,25 @@ "env": { "jest/globals": true }, - "settings": { - "import/resolver": { - "node": { - "extensions": [ - ".js", - ".jsx", - ".ts", - ".tsx", - ".njs", - ".nts" - ] + "rules": { + "nullstack/prettier": [ + "warn", + { + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "singleQuote": true, + "printWidth": 120 + }, + { + "usePrettierrc": false } - } + ] }, "globals": { "page": "writable", "browser": "writable" }, - "rules": { - "no-console": "off" - }, "ignorePatterns": [ "temp.js", "types/*.d.ts", diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 729f927c..00000000 --- a/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "trailingComma": "all", - "tabWidth": 2, - "semi": false, - "singleQuote": true, - "printWidth": 120 -} \ No newline at end of file diff --git a/builders/spa.js b/builders/spa.js index 61f8d18e..c7eeb28b 100644 --- a/builders/spa.js +++ b/builders/spa.js @@ -9,7 +9,7 @@ module.exports = async function spa({ output, cache, environment }) { const path = `${dir}/${folder}` async function copy(url, file) { - console.log(` ⚙️ ${file || url}`) + console.info(` ⚙️ ${file || url}`) const content = await application.server.prerender(url) const target = `${dir}/${folder}${file || url}` writeFileSync(target, content) @@ -19,25 +19,25 @@ module.exports = async function spa({ output, cache, environment }) { return dest.endsWith(folder) || (src.includes('client') && !src.includes('.txt')) } - console.log() + console.info() if (existsSync(path)) { removeSync(path) } mkdirSync(path) - console.log(` ⚙️ /public/`) + console.info(` ⚙️ /public/`) copySync(`${dir}/public`, path) await copy('/', '/index.html') - console.log(` ⚙️ /.${environment}/`) + console.info(` ⚙️ /.${environment}/`) copySync(`${dir}/.${environment}`, path, { filter }) await copy(`/manifest.webmanifest`) await copy(`/service-worker.js`) await copy('/robots.txt') - console.log() + console.info() - console.log('\x1b[36m%s\x1b[0m', ` ✅️ ${projectName} is ready at ${folder}\n`) + console.info('\x1b[36m%s\x1b[0m', ` ✅️ ${projectName} is ready at ${folder}\n`) if (cache) { - console.log('Storing cache...') + console.info('Storing cache...') } else if (environment === 'production') { process.exit() } diff --git a/builders/ssg.js b/builders/ssg.js index 25dcbae7..bd13fdbc 100644 --- a/builders/ssg.js +++ b/builders/ssg.js @@ -24,7 +24,7 @@ module.exports = async function ssg({ output, cache, environment }) { const content = await application.server.prerender(url) const target = path(url) - console.log(` ⚙️ ${url}`) + console.info(` ⚙️ ${url}`) if (!existsSync(target)) { mkdirSync(target, { recursive: true }) } @@ -67,14 +67,14 @@ module.exports = async function ssg({ output, cache, environment }) { } async function copyBundle(url) { - console.log(` ⚙️ ${url}`) + console.info(` ⚙️ ${url}`) const content = await application.server.prerender(url) const target = path(url) writeFileSync(target, content) } async function createSitemap() { - console.log(' ⚙️ /sitemap.xml') + console.info(' ⚙️ /sitemap.xml') const timestamp = new Date().toJSON().substring(0, 10) const urls = Object.keys(pages).map((p) => { const page = pages[p] @@ -93,14 +93,14 @@ module.exports = async function ssg({ output, cache, environment }) { return dest.endsWith(folder) || (src.includes('client') && !src.includes('.txt')) } - console.log() + console.info() if (existsSync(path())) { removeSync(path()) } mkdirSync(path()) - console.log(` ⚙️ /public/`) + console.info(` ⚙️ /public/`) copySync(path(`../public`), path()) - console.log(` ⚙️ /.${environment}/`) + console.info(` ⚙️ /.${environment}/`) copySync(path(`../.${environment}`), path(), { filter }) await copyRoute() await copyRoute(`/nullstack/${application.environment.key}/offline`) @@ -109,12 +109,12 @@ module.exports = async function ssg({ output, cache, environment }) { await copyBundle(`/service-worker.js`) await copyBundle('/robots.txt') await createSitemap() - console.log() + console.info() - console.log('\x1b[36m%s\x1b[0m', ` ✅️ ${projectName} is ready at ${folder}\n`) + console.info('\x1b[36m%s\x1b[0m', ` ✅️ ${projectName} is ready at ${folder}\n`) if (cache) { - console.log('Storing cache...') + console.info('Storing cache...') } else if (environment === 'production') { process.exit() } diff --git a/builders/ssr.js b/builders/ssr.js index 7194b85d..eb3ead35 100644 --- a/builders/ssr.js +++ b/builders/ssr.js @@ -3,10 +3,10 @@ module.exports = async function ssr({ cache }) { const application = require(`${dir}/.production/server`).default const projectName = application.project.name || 'The Nullstack application' - console.log('\x1b[36m%s\x1b[0m', `\n ✅️ ${projectName} is ready for production\n`) + console.info('\x1b[36m%s\x1b[0m', `\n ✅️ ${projectName} is ready for production\n`) if (cache) { - console.log('Storing cache...') + console.info('Storing cache...') } else { process.exit() } diff --git a/client/events.js b/client/events.js index 23732e82..2027fb56 100644 --- a/client/events.js +++ b/client/events.js @@ -8,7 +8,6 @@ export const eventDebouncer = new WeakMap() function executeEvent(callback, subject, event, data) { if (typeof callback === 'object') { - console.log(subject, callback) Object.assign(subject.source, callback) } else { callback({ ...subject, event, data }) diff --git a/client/render.js b/client/render.js index b85ff893..e4b6276d 100644 --- a/client/render.js +++ b/client/render.js @@ -35,7 +35,6 @@ export default function render(node, options) { const eventName = name.substring(2) const callback = generateCallback(node.element, name) node.element.addEventListener(eventName, callback) - console.log(node.attributes.onclick) eventCallbacks.set(node.element, callback) eventSubjects.set(node.element, node.attributes) } diff --git a/client/worker.js b/client/worker.js index 19175f08..3a079dfd 100644 --- a/client/worker.js +++ b/client/worker.js @@ -39,7 +39,7 @@ async function register() { try { proxy.registration = await navigator.serviceWorker.register(request, { scope: '/' }) } catch (error) { - console.log(error) + console.error(error) } } } diff --git a/loaders/add-source-to-node.js b/loaders/add-source-to-node.js index 8ab4bb64..435306c6 100644 --- a/loaders/add-source-to-node.js +++ b/loaders/add-source-to-node.js @@ -1,12 +1,41 @@ +const parse = require('@babel/parser').parse +const traverse = require('@babel/traverse').default + module.exports = function (source) { - const tags = source.split('<') - return tags - .map((tag) => { - const match = tag.match(/ on([a-z]*?)=\{(.*?)\}/) - if (match && tag.indexOf('source={') === -1) { - return `${tag.substring(0, match.index)} source={this}${tag.substring(match.index)}` + this.cacheable && this.cacheable() + const uniquePositions = new Set() + const ast = parse(source, { + sourceType: 'module', + plugins: ['classProperties', 'jsx', 'typescript'], + }) + traverse(ast, { + JSXIdentifier(path) { + if (path.parent.type === 'JSXAttribute') { + if (path.node.name.startsWith('on')) { + const element = path.findParent((p) => p.type === 'JSXOpeningElement' && p.node.attributes) + const hasSource = element.node.attributes.find((a) => a.name.name === 'source') + if (!hasSource) { + const start = element.node.attributes[0].start + uniquePositions.add(start) + } + } } - return tag - }) - .join('<') + }, + }) + if (uniquePositions.size === 0) return source + const positions = [...uniquePositions] + positions.reverse() + positions.push(0) + const outputs = [] + let last + for (const position of positions) { + const code = source.slice(position, last) + last = position + outputs.push(code) + if (position) { + outputs.push(`source={this} `) + } + } + + return outputs.reverse().join('') } diff --git a/package.json b/package.json index 06d6b0f8..83979713 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "css-loader": "6.7.1", "dotenv": "8.6.0", "eslint-plugin-jest": "^27.1.6", - "eslint-plugin-nullstack": "^0.0.4", + "eslint-plugin-nullstack": "^0.0.6", "express": "4.18.1", "fs-extra": "10.1.0", "mini-css-extract-plugin": "2.6.0", diff --git a/scripts/index.js b/scripts/index.js index 4b28307d..23fae58b 100755 --- a/scripts/index.js +++ b/scripts/index.js @@ -60,7 +60,7 @@ function clearOutput(outputPath) { async function start({ input, port, env, mode = 'spa', cold, disk, loader = 'swc' }) { const environment = 'development' - console.log(` 🚀️ Starting your application in ${environment} mode...`) + console.info(` 🚀️ Starting your application in ${environment} mode...`) loadEnv(env) const WebpackDevServer = require('webpack-dev-server') const { setLogLevel } = require('webpack/hot/log') @@ -107,9 +107,9 @@ async function start({ input, port, env, mode = 'spa', cold, disk, loader = 'swc middlewares.unshift(async (req, res, next) => { if (req.originalUrl.indexOf('.hot-update.') === -1) { if (req.originalUrl.startsWith('/nullstack/')) { - console.log(` ⚙️ [${req.method}] ${req.originalUrl}`) + console.info(` ⚙️ [${req.method}] ${req.originalUrl}`) } else { - console.log(` 🕸️ [${req.method}] ${req.originalUrl}`) + console.info(` 🕸️ [${req.method}] ${req.originalUrl}`) } } async function waitForServer() { @@ -140,7 +140,7 @@ async function start({ input, port, env, mode = 'spa', cold, disk, loader = 'swc const portChecker = require('express')().listen(process.env.NULLSTACK_SERVER_PORT, () => { portChecker.close() server.startCallback(() => { - console.log( + console.info( '\x1b[36m%s\x1b[0m', ` ✅️ Your application is ready at http://${process.env.NULLSTACK_PROJECT_DOMAIN}:${process.env.NULLSTACK_SERVER_PORT}\n`, ) @@ -154,10 +154,10 @@ function build({ input, output, cache, env, mode = 'ssr' }) { if (env) { process.env.NULLSTACK_ENVIRONMENT_NAME = env } - console.log(` 🚀️ Building your application in ${mode} mode...`) + console.info(` 🚀️ Building your application in ${mode} mode...`) compiler.run((error, stats) => { if (stats.hasErrors()) { - console.log(stats.toString({ colors: true })) + console.info(stats.toString({ colors: true })) process.exit(1) } if (stats.hasErrors()) process.exit(1) diff --git a/server/printError.js b/server/printError.js index 83dbed61..a2daa484 100644 --- a/server/printError.js +++ b/server/printError.js @@ -28,17 +28,17 @@ export default function (error) { } } } - console.log() - console.log('\x1b[31m', error.name, '-', error.message, '\x1b[0m') - console.log() + console.info() + console.info('\x1b[31m', error.name, '-', error.message, '\x1b[0m') + console.info() if (initiator) { - console.log('\x1b[2m', 'initiator:', '\x1b[0m', '\x1b[37m', initiator, '\x1b[0m') + console.info('\x1b[2m', 'initiator:', '\x1b[0m', '\x1b[37m', initiator, '\x1b[0m') } if (file) { - console.log('\x1b[2m', 'file: ', '\x1b[0m', '\x1b[37m', file, '\x1b[0m') + console.info('\x1b[2m', 'file: ', '\x1b[0m', '\x1b[37m', file, '\x1b[0m') } if (line) { - console.log('\x1b[2m', 'line: ', '\x1b[0m', '\x1b[37m', line, '\x1b[0m') + console.info('\x1b[2m', 'line: ', '\x1b[0m', '\x1b[37m', line, '\x1b[0m') } - console.log() + console.info() } diff --git a/server/server.js b/server/server.js index 67e20d9f..13a71990 100644 --- a/server/server.js +++ b/server/server.js @@ -252,7 +252,7 @@ server.start = function () { if (!server.less) { if (!server.port) { - console.log('\x1b[31mServer port is not defined!\x1b[0m') + console.info('\x1b[31mServer port is not defined!\x1b[0m') process.exit() } @@ -268,7 +268,7 @@ server.start = function () { socket.send('{"type":"NULLSTACK_SERVER_STARTED"}') } } else { - console.log( + console.info( '\x1b[36m%s\x1b[0m', ` ✅️ Your application is ready at http://${process.env.NULLSTACK_PROJECT_DOMAIN}:${process.env.NULLSTACK_SERVER_PORT}\n`, ) diff --git a/shared/deserialize.js b/shared/deserialize.js index f59b9496..4dc6f181 100644 --- a/shared/deserialize.js +++ b/shared/deserialize.js @@ -1,5 +1,5 @@ const reISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/ -const reMsAjax = /^\/Date\((d|-|.*)\)[\/|\\]$/ +const reMsAjax = /^\/Date\((d|-|.*)\)[/|\\]$/ function dateParser(key, value) { if (typeof value === 'string') { diff --git a/tests/src/scripts/run.js b/tests/src/scripts/run.js index a4e68859..34205d89 100644 --- a/tests/src/scripts/run.js +++ b/tests/src/scripts/run.js @@ -5,7 +5,7 @@ const { default: application } = require('../../.production/server.js') async function getProjectName() { await application.start() const { project } = application - console.log(project.name) + console.info(project.name) } getProjectName() From 33b28e3a2d12927c0cf91f3d846d8eb1bf42ab6c Mon Sep 17 00:00:00 2001 From: Christian Mortaro Date: Sun, 4 Dec 2022 13:36:07 -0300 Subject: [PATCH 8/9] :bookmark: version 0.16.7 --- package.json | 4 ++-- tests/README.md | 18 +++--------------- tests/package.json | 4 +++- webpack.config.js | 2 +- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 83979713..fdc23a76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nullstack", - "version": "0.16.6", + "version": "0.16.7", "description": "Full-stack Javascript Components for one-dev armies", "main": "nullstack.js", "author": "Mortaro", @@ -33,7 +33,7 @@ "css-loader": "6.7.1", "dotenv": "8.6.0", "eslint-plugin-jest": "^27.1.6", - "eslint-plugin-nullstack": "^0.0.6", + "eslint-plugin-nullstack": "^0.0.10", "express": "4.18.1", "fs-extra": "10.1.0", "mini-css-extract-plugin": "2.6.0", diff --git a/tests/README.md b/tests/README.md index 8f3abd5f..a45308cb 100644 --- a/tests/README.md +++ b/tests/README.md @@ -6,26 +6,14 @@ This folder contains tests for Nullstack using Jest and Puppeteer. ## How to install -In project folder: +In the tests folder: ```sh -npm install -npm link -cd tests -npm install -npm link nullstack +npm run setup ``` ## How to run the tests -`cd` to the `tests` folder and run the application: - -```sh -npm start -``` - -in another terminal `cd` to the `tests` folder and run the tests: - ```sh -npm test +npm run test ``` \ No newline at end of file diff --git a/tests/package.json b/tests/package.json index 8da74831..b81955bc 100644 --- a/tests/package.json +++ b/tests/package.json @@ -16,10 +16,12 @@ "scripts": { "start": "npx nullstack start --input=./tests --port=6969 --env=test --mode=spa", "build": "npx nullstack build --input=./tests --env=test", + "clear": "rm -rf ../node_modules ../package-lock.json node_modules .development .production package-lock.json", + "setup": "cd .. && npm install && npm link && cd tests && npm install && npm link nullstack", "test": "npm run build && jest --runInBand", "script": "node src/scripts/run.js" }, "dependencies": { "eslint-plugin-nullstack": "^0.0.3" } -} +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index edb5ad22..99930167 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -432,7 +432,7 @@ function client(env, argv) { use: [ MiniCssExtractPlugin.loader, { loader: require.resolve('css-loader'), options: { url: false } }, - { loader: require.resolve('sass-loader') }, + { loader: require.resolve('sass-loader'), options: { sassOptions: { fibers: false } } }, ], }, { From d72e92a155cb324e71a38df946b818d9bd77e224 Mon Sep 17 00:00:00 2001 From: Christian Mortaro Date: Sun, 4 Dec 2022 13:59:31 -0300 Subject: [PATCH 9/9] :bookmark: version 0.17.0 --- package.json | 3 +-- server/server.js | 3 --- tests/package.json | 1 + tests/server.js | 11 +++++++++-- tests/src/ServerRequestAndResponse.njs | 7 ------- types/Server.d.ts | 5 ----- 6 files changed, 11 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index fdc23a76..708e3994 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nullstack", - "version": "0.16.7", + "version": "0.17.0", "description": "Full-stack Javascript Components for one-dev armies", "main": "nullstack.js", "author": "Mortaro", @@ -29,7 +29,6 @@ "body-parser": "1.20.0", "commander": "8.3.0", "copy-webpack-plugin": "^11.0.0", - "cors": "2.8.5", "css-loader": "6.7.1", "dotenv": "8.6.0", "eslint-plugin-jest": "^27.1.6", diff --git a/server/server.js b/server/server.js index 13a71990..ef823bb2 100644 --- a/server/server.js +++ b/server/server.js @@ -1,5 +1,4 @@ import bodyParser from 'body-parser' -import cors from 'cors' import express from 'express' import { writeFileSync } from 'fs' import fetch from 'node-fetch' @@ -154,8 +153,6 @@ server.start = function () { if (serverStarted) return serverStarted = true - server.use(cors(server.cors)) - server.use(express.static(path.join(__dirname, '..', 'public'))) server.use(bodyParser.text({ limit: server.maximumPayloadSize })) diff --git a/tests/package.json b/tests/package.json index b81955bc..219dc4df 100644 --- a/tests/package.json +++ b/tests/package.json @@ -5,6 +5,7 @@ "author": "", "license": "ISC", "devDependencies": { + "cors": "2.8.5", "glob": "^8.0.3", "jest": "^28.1.0", "jest-puppeteer": "^6.1.0", diff --git a/tests/server.js b/tests/server.js index f5465f38..3922bd82 100644 --- a/tests/server.js +++ b/tests/server.js @@ -1,5 +1,7 @@ import Nullstack from 'nullstack' +import cors from 'cors' + import Application from './src/Application' import ContextProject from './src/ContextProject' import ContextSecrets from './src/ContextSecrets' @@ -7,7 +9,6 @@ import ContextSettings from './src/ContextSettings' import ContextWorker from './src/ContextWorker' import ExposedServerFunctions from './src/ExposedServerFunctions' import vueable from './src/plugins/vueable' -import ServerRequestAndResponse from './src/ServerRequestAndResponse' Nullstack.use(vueable) @@ -15,6 +16,13 @@ const context = Nullstack.start(Application) const methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT'] +context.server.use( + cors({ + origin: 'http://localhost:6969', + optionsSuccessStatus: 200, + }), +) + context.server.get('/data/all/:param', ExposedServerFunctions.getData) context.server.get('/data/get/:param', ExposedServerFunctions.getData) context.server.post('/data/post/:param', ExposedServerFunctions.getData) @@ -46,7 +54,6 @@ context.start = async function () { await ContextSecrets.start(context) await ContextSettings.start(context) await ContextWorker.start(context) - await ServerRequestAndResponse.start(context) context.startValue = true context.startIncrementalValue++ } diff --git a/tests/src/ServerRequestAndResponse.njs b/tests/src/ServerRequestAndResponse.njs index 1503ce08..67e0deb9 100644 --- a/tests/src/ServerRequestAndResponse.njs +++ b/tests/src/ServerRequestAndResponse.njs @@ -4,13 +4,6 @@ class ServerRequestAndResponse extends Nullstack { responses = {}; - static async start({ server }) { - server.cors = { - origin: 'http://localhost:6969', - optionsSuccessStatus: 200, - } - } - async hydrate() { const responses = {} const methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT'] diff --git a/types/Server.d.ts b/types/Server.d.ts index 745f978c..b6470951 100644 --- a/types/Server.d.ts +++ b/types/Server.d.ts @@ -18,9 +18,4 @@ export type NullstackServer = { port: number maximumPayloadSize: string - - /** - * Will be passed as the argument to [express cors plugin](https://expressjs.com/en/resources/middleware/cors.html). - */ - cors: object }