NativeScript icon indicating copy to clipboard operation
NativeScript copied to clipboard

Add SolidJS to list of frameworks

Open davedbase opened this issue 4 years ago • 10 comments

Is your feature request related to a problem? Please describe.

Hailing from the SolidJS community! We'd love to see NativeScript support Solid's fine-grained reactivity. It brings a simplicity and extreme performance to the table and would be an excellent addition for a mobile environment. You can read more about solid at https://www.solidjs.com.

Describe the solution you'd like

We'd like to select SolidJS as an additional framework beside Vue, React, Svelte, Ionic etc. when setting up NativeScript. Solid is rather similar to React in setup. It's JSX-based and uses a Babel transformer to compile down to runnable JS. We have a number of starters that demonstrate how to set it up.

Describe alternatives you've considered

No response

Anything else?

I'd be more than happy to submit a PR to add Solid however I can't seem to find instructions or documentation on how to go about it or where to start. Direction and guidance on this would be much appreciated.

Please accept these terms

davedbase avatar Dec 31 '21 15:12 davedbase

Looks really nice and clean - @shirakaba given your experience with tsx integration would be curious your input here. I would imagine using this https://github.com/shirakaba/nside/tree/master/app as a guide to setup may give solidjs users a good start to setup a project.

NathanWalker avatar Dec 31 '21 17:12 NathanWalker

I can speak for the angular side for you.

Basically what you need is to write a renderer that translates your tsx and other operations to nativescript views.

This is a good starting point:

let frameworkInstance;
Application.on('launch', (args) => {
  // bootstrap the framework and extract the root view
  args.root = yourRootView;
});
Application.on('exit', () => {
  frameworkInstance.destroy();
});

Application.run();

For angular we do a few special things. Since we need comment nodes we have special views called CommentNode and TextNode which are never added to the NativeScript dom, and we also augment views with some DOM apis (nextSibling, previousSibling, firstChild...), See: https://github.com/NativeScript/angular/blob/619f4c29bc0ab071d8359b8de5ddf256b8994ad1/packages/angular/src/lib/views/view-types.ts

This is our full bootstrap code for angular (it's messy because of HMR and some async things we're handling): https://github.com/NativeScript/angular/blob/619f4c29bc0ab071d8359b8de5ddf256b8994ad1/packages/angular/src/lib/application.ts

If you need your app to bootstrap async, you can do args.root = null and set the rootview later. Just beware that it may cause flashes. We're introducing a new API to drain microtasks manually to help with that.

Feel free to ping me on discord or slack and I can go into more details.

edusperoni avatar Dec 31 '21 17:12 edusperoni

@davedbase I'll copy out the discussion we had over Discord, for posterity:


Even if Solid.JS offers a custom renderer API, you would still very likely benefit from simply implementing DOM and using solidjs/web as-is, rather than implementing the renderer APIs for each and every UI component. Mainly because this would give users an imperative API to work with, but also because it would keep the code of the renderer itself minimal.

For some general reading around this subject, here's an unfinished blog post I wrote up about the many UI renderers of NativeScript.

As for implementing Solid.JS APIs like insertNode, unfortunately this is best handled on a case-by-case basis. The closest thing NativeScript has to a generic DOM-like appendChild() method is named something like _addChildToBuilder(), but although it's suitable in many cases, it sometimes doesn't fit the bill. You could refer to or re-use my DOM implementation for React NativeScript which lives here. Just be warned that Frame and Page will put up a fight – we can help you with navigation once it comes to it!

As for your startup function, here's how I'd write it:

Given this entrypoint App.tsx from the userland code:

import { renderNativeScript } from '@davedbase/solid-nativescript';

function HelloWorld(){
    return (
        <gridLayout>
            <label>Hello World!</label>
        </gridLayout>
    );
}

renderNativeScript(HelloWorld);

... here's how you'd implement @davedbase/solid-nativescript/index.tsx:

import { FlexboxLayout, run } from "@nativescript/core";
import type { View } from "@nativescript/core";
import { render } from 'solid-js/web';

export function renderNativeScript(
    // (I don't know the typings for a Solid.JS component)
    AppComponent: any,
    rootView: View = new FlexboxLayout()
): void {
    run({
        create: () => {
            render(
                // Or should it be:
                // () => <AppComponent/>
                // I forget..!
                AppComponent,
                rootView,
            );

            // This synchronously passes the root view to NativeScript.
            return rootView;
        },
    });
}

As for a Webpack config, here's how one would transpile TSX (putting aside the question of Solid.JS support for the moment):

import { merge } from 'webpack-merge';
import Config from 'webpack-chain';

import { getPlatformName } from '../helpers/platform';
import { env as _env, IWebpackEnv } from '../index';
// @see https://github.com/NativeScript/NativeScript/blob/master/packages/webpack5/src/configuration/base.ts
import base from './base';

export default function (config: Config, env: IWebpackEnv = _env): Config {
    base(config, env);

    const platform = getPlatformName();
    const mode = env.production ? 'production' : 'development';
    const production = mode === 'production';

    // todo: use env
    let isAnySourceMapEnabled = true;

    config.resolve.extensions.prepend('.tsx').prepend(`.${platform}.tsx`);

    config.module
        .rule('ts')
        .test([...config.module.rule('ts').get('test'), /\.tsx$/]);

    // todo: env flag to forceEnable?
    config.when(env.hmr && !production, (config) => {
        config.module
            .rule('ts')
            .use('babel-loader')
            .loader('babel-loader')
            .before('ts-loader')
            .options({
                sourceMaps: isAnySourceMapEnabled ? 'inline' : false,
                babelrc: false,
                plugins: [],
            });
    });

    return config;
}

Based on the work-in-progress Webpack config for Solid.JS you gave on Discord, I'd recommend:

  • Leave out @babel/env for now; we can talk about polyfills later;
  • Leave out @babel/typescript and let ts-loader handle purely TypeScript files – I think that's the only loader that can handle our @NativeClass decorator;
  • So you'd just need to add your solid preset in somewhere (not too sure about order; can just try a few orders and see which one doesn't explode).

As for the NativeScript lifecycle, here's the extent to which I understand it (which is more than enough to build a renderer, but other folks here may have points to correct):

  1. Some native (iOS or Android) code runs first. So you'll have access to your startup args in native-land before any NativeScript code runs, for one thing. Part of that native code will be to initialise the NativeScript runtime (based on V8 or JavaScriptCore). This is a relatively heavy task.

  2. I guess as soon as it's initialised, NativeScript starts subscribing to lifecycle functions such as the event/message you'd get when your phone is rotated, or a deeplink is clicked from outside the app, or a native notification is received, or the app is being closed.

  3. Quickly after that, the native side calls the JS create() function that you see me implement in my call to run which I imported from @nativescript/core in my example implementation of renderNativeScript().

That's the point at which NativeScript gets some control over the UI. Adjusting the UI before then, beyond the API for setting a startup image that iOS and Android offer, is surely complicated!

shirakaba avatar Jan 01 '22 17:01 shirakaba

Wow @shirakaba this is a really awesome push! Thank you so much for all the detail. NativeScript has been brought up in our Discord on more than one occasion and now I have some information regarding implementation to bring back to our community.

There are quite a few community members very excited and serious about making this happen. The concern regarding appendChild is something we could potentially run by @ryansolid about in case there's a workaround we can consider.

Thanks! Looking forward to having a solid-nativescript :-)

davedbase avatar Jan 01 '22 19:01 davedbase

Thanks @shirakaba for detailed explanation in discord of NativeScript and Solid.JS! I did proof-of-concept. https://github.com/MrFoxPro/solid-nativescript-experiments solidnative2

If folks from @solidjs don't mind, I can continue development in the near future.

0x241F31 avatar Jan 02 '22 02:01 0x241F31

Really excellent @MrFoxPro 👏

NathanWalker avatar Jan 02 '22 03:01 NathanWalker

@MrFoxPro Is this SolidJS-NativeScript integration production-ready?

svicalifornia avatar Jun 09 '22 20:06 svicalifornia

It looks like it's still in proof-of-concept stage (doesn't support navigation, for example).

I do apologise as I suggested that the way to progress on this was to build a common DOM implementation for NativeScript, and then let the Solid adapter just manipulate that. I made a few attempts (see the various branches here) at creating one by forking jsdom, but it's a really hard library to disentangle, so I never got as far as a working example. Once I've got some free time again and once I can get Python working on my M1 Mac again (required for running NativeScript on the iOS simulator), I was hoping to try again using happy-dom as a basis instead.

shirakaba avatar Jun 09 '22 23:06 shirakaba

It looks like it's still in proof-of-concept stage (doesn't support navigation, for example).

I do apologise as I suggested that the way to progress on this was to build a common DOM implementation for NativeScript, and then let the Solid adapter just manipulate that. I made a few attempts (see the various branches here) at creating one by forking jsdom, but it's a really hard library to disentangle, so I never got as far as a working example. Once I've got some free time again and once I can get Python working on my M1 Mac again (required for running NativeScript on the iOS simulator), I was hoping to try again using happy-dom as a basis instead.

IDK if undom is sufficient for solidjs, but it's pretty promising with ef.js with a little modification. It's very simple and worth a look.

ClassicOldSong avatar Aug 02 '22 13:08 ClassicOldSong

Here, I've made a DOM compatibility layer for NativeScript and it works well with two of my own frameworks. Maybe you can try some more frameworks including SolidJS. I need more test cases to see if there's anything missing or needs improvement.

https://github.com/SudoMaker/DOMiNATIVE

ClassicOldSong avatar Aug 26 '22 18:08 ClassicOldSong

Hello! I was just wondering if there was any progress on this or if there was a timeline for having a NativeScript version for solidjs?

tkdaj avatar Feb 04 '23 06:02 tkdaj

Hello! I was just wondering if there was any progress on this or if there was a timeline for having a NativeScript version for solidjs?

There already is:

https://github.com/nativescript-community/solid-js

ClassicOldSong avatar Feb 04 '23 11:02 ClassicOldSong