diff --git a/.env b/.env new file mode 100644 index 00000000..fefc515a --- /dev/null +++ b/.env @@ -0,0 +1 @@ +NULLSTACK_SERVER_PORT=5000 \ No newline at end of file diff --git a/.github/workflows/ssg-build.yml b/.github/workflows/ssg-build.yml new file mode 100644 index 00000000..f32d4a95 --- /dev/null +++ b/.github/workflows/ssg-build.yml @@ -0,0 +1,42 @@ +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: SSG Build to Deploy + +on: + push: + branches: [ master ] + workflow_dispatch: + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + # cache the dependencies from any node_modules directory + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: | + **/node_modules + **/package-lock.json + **/.production + key: node_modules-${{ hashFiles('**/package.json') }} + + - name: Install deps + run: npm install + + - name: Build + run: npx nullstack build --mode=ssg --output=docs + + - name: Send to Github + run: | + git branch gh-pages -f + git checkout gh-pages + touch ./docs/.nojekyll + git add docs -f + git config --global user.name "NullstackDeployer" + git config --global user.email "nullstack@deployer.ci" + git commit -m ":rocket: New SSG Build" + git push -u origin gh-pages -f \ No newline at end of file diff --git a/.gitignore b/.gitignore index e0cf2191..81cf24c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ node_modules package-lock.json +yarn.lock .development -.production \ No newline at end of file +.production +public/pt-BR.json +public/en-US.json +docs diff --git a/README.br.md b/README.br.md new file mode 100644 index 00000000..2ff90cac --- /dev/null +++ b/README.br.md @@ -0,0 +1,21 @@ +# Documentação do Nullstack + +Nullstack + +## Como executar este projeto + +Instale as dependências: + +`npm install` + +Rode a aplicação no modo de desenvolvimento: + +`npm start` + +Abra [http://localhost:5000](http://localhost:5000) para vê-lo no navegador. + +## Aprenda mais sobre o Nullstack + +[Leia a documentação](https://nullstack.app/pt-br/comecando) + + Read in English \ No newline at end of file diff --git a/README.md b/README.md index 3e142c06..196e83fb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,21 @@ +# Nullstack Documentation + Nullstack -## Welcome to Nullstack +## How to run this Project + +Install the dependencies: + +`npm install` + +Runs the app in the development mode: + +`npm start` + +Open [http://localhost:5000](http://localhost:5000) to view it in the browser. + +## Learn more about Nullstack + +[Read the documentation](https://nullstack.app/getting-started) -[Read the documentation](https://github.com/nullstack/nullstack) \ No newline at end of file + Leia em Português \ No newline at end of file diff --git a/articles/about.md b/articles/about.md deleted file mode 100644 index c553a0e8..00000000 --- a/articles/about.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -title: Why Nullstack Exists -description: The sole purpose of Nullstack is to simplify the development by eliminating glue code and letting you focus on the logic of your application ---- - -The sole purpose of Nullstack is to simplify the development by eliminating glue code and letting you focus on the logic of your application. - -It was created keeping in mind programmers used to developing entire systems alone, but it is easily scalable to small or even big teams, as long as each programmer knows the flow of the feature they are to develop. - -## The Stack - -With the stack of technologies used in the web nowadays, the most common flow is something like this: - -- Front-end uses a reducer over a context that calls a fetcher; -- This fetcher brings generic information over a RESTful API; -- The RESTful API calls a server route, which calls a controller, which takes the information of a model and resolves it into a serializer; -- If you need more than one resource, this process should be repeated until all resources are fetched; -- After all the data has been fetched, only then should the front-end be able to use it; -- Reason about how to deal with server-side render and hydration of the steps above; - -Note that all you wanted was to show something from the database into a view. With Nullstack, that’s all you need to concern yourself with. Everything else is “glue code” and the framework should take care of it for you. - -## Feature-driven - -If you’re used to working on more than one project at a time or even if you just happen to have to give sporadic maintenance to a lot of your old projects, you might have stumbled upon this scenario: you don’t remember exactly where in your code is the logic you’re trying to fix or improve. - -You might have a hook whose dependencies are local variables initialized with a redux state, which was stored at some point by an action declared somewhere in your source tree and called in who knows where. - -If everything pertaining to a single feature were to be in the same file, maybe you wouldn’t need to reverse engineer your own code every time you need to update or fix something. - -Putting everything in a single file may sound messy at a glance, but remember that you are the one who decides the granularity of this division. - -A "feature" might be an entire register form or something as small as a button that does some verifications before letting you submit that form. It’s entirely up to you, and since each component is as complete as an entire feature, you could call this button or even the entire form on other pages in your application. This leads us to **True Componentization and Code Reusability**. - -## Componentization and Code Reusability - -Components in Nullstack are self-sufficient. - -Most frameworks are specialized in a single layer, meaning that any component will be only as complete as its framework. When exporting a Nullstack component, all the code needed to run the feature is going to be together, without the need of allocating the other layers separately. - -As a side effect, entire applications can be used as components, and mounted in other applications as engines. - -## Why object-oriented instead of functional - -At first glance, classes may look more verbose than the trendy functional components. -This section will explain the reasons that lead us to favor classes in the development of Nullstack. - -The reasons are actually connected to some core principles of Nullstack, being: - -### Everything as Vanilla as Possible - -We didn’t want to introduce a “Nullstack way” of doing things and wanted it to be accessible to anyone with some Javascript knowledge. - -That being said, the first big problem was to address state management in a vanilla Javascript way. Supporting functional components would require a solution similar to the hooks of React.js that would be considered a mannerism of the framework. - -Since we opted out of immutability as a framework constraint, we are allowed to use the native way of setting simple variables. This removes the complexity of state management that created the need of third-party state management libraries in the first place. - -### No Glue Code or “Batteries Included” - -Nullstack borrows the concept of “battery-included” from Ember.js, but allows you to change batteries. Everything you need to make an application should be part of the framework, and still be flexible. - -A framework should do the heavy lifting and a programmer should focus on his own application. -For this reason, all you have to do is to declare your classes and let Nullstack instantiate them for you. That way, we removed the most painful aspect of dealing with classes while maintaining all of the advantages of them. - -### Having a safe escape route - -Object-oriented versus functional is not a new topic, and lately the former seems to be bullied out of most frameworks, leaving no place for developers that enjoy this pattern. - -Admittedly classes took too long to be standardized into Javascript and the delay might have caused some traumatic bad implementations along the way. - -While object-oriented programming might not be the best solution for every problem, Nullstack allows you to import functions freely and use them in the moments when they should be the weapon of choice. - -## Why dependency injection instead of modularity - -Nullstack context uses the dependency injection pattern, which means that everything you need can be requested from the framework at the signature level of the function. - -The context is a horizontally scoped object that is injected in all of your function calls. The non-hierarchical nature of this pattern allows you to easily move around your component's logic as your application grows, while still avoiding problems like props drilling or filling your view layer with store declarations. - -This has two major advantages: - -- You see the dependencies of your code at a function level instead of having them all imported on top of the file. - -- The framework is able to give you the most precise information about the specific environment for that function call. - -## Developer Happiness - -The generated application is enough to have a PWA without thinking about boilerplates, but you are completely free to override the default behavior of each moving piece. - -A borrowed concept from Ruby is developer happiness. Nullstack aims to ease the developer’s life by simplifying everything possible, but without hiding things from you. - -The first developers we wanted to make happy are ourselves. We made Nullstack because we had fun in the process. It started as a simple prototype on top of React.js and we got carried away, each time making it more enjoyable for us until it became its own thing. - -We hope you enjoy using Nullstack as much as we do because that's what keeps this project going forward. diff --git a/articles/application-startup.md b/articles/application-startup.md deleted file mode 100644 index d64d51f4..00000000 --- a/articles/application-startup.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Application Startup -description: The start function will run only once when your application is booted and is a good place for setting up your server context ---- - -The index.js file at your application root is responsible for starting your application. - -When you run the application with *npm start* or *node .production/server.js* the index will call the start function in your *src/Application.js*. - -The start function will run only once when your application is booted and is a good place for setting up your [server context](/context). - -```jsx -import Nullstack from 'nullstack'; -import database from './database'; - -class Application extends Nullstack { - - static async start(context) { - context.database = database; - } - -} - -export default Application; -``` - -## Dependency startup pattern - -A nice pattern to work with dependencies that require startup time configurations is to define a start function in the dependency and call it in the Application start function passing the [server context](/context). - -```jsx -import Nullstack from 'nullstack'; -import Dependency from './Dependency'; - -class Application extends Nullstack { - - static async start(context) { - Dependency.start(context); - } - -} - -export default Application; -``` - -> 🔒 Server functions with the name starting with "start" (and optionally followed by an uppercase letter) do not generate an API endpoint to avoid malicious context flooding. - -## Next step - -⚔ Learn about the [context data](/context-data). \ No newline at end of file diff --git a/articles/context-data.md b/articles/context-data.md deleted file mode 100644 index 9181ddd4..00000000 --- a/articles/context-data.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Context Data -description: The data is an object in the framework store part of your context and gives you information about the element dataset. ---- - -The data is an object in the framework store part of your context and gives you information about the element dataset. - -You can use this key to avoid polluting your DOM with invalid attributes. - -> 💡 This helps Nullstack set attributes without wasting time validating them. - -This key is *readonly* and available only in the *client* context. - -Any *data-\** attributes will receive a respective camelized key on the data object. - -You can assign data attributes both via data-* and a data key that accepts an object with camelized keys. - -The kebab version is also available in the context. - -```jsx -import Nullstack from 'nullstack'; - -class ContextData extends Nullstack { - - count = 1; - - calculate({data}) { - this.count = this.count * data.multiply + data.sum; - } - - renderInner(context) { - const {data} = context; - return ( -
- {data.frameworkName} - is same as - {context['data-framework-name']} -
- ) - } - - render({data}) { - return ( -
- - -
- ) - } - -} - -export default ContextData; -``` - -> 💡 Camelized keys from the data object will result in kebab attributes in the DOM. - -## Next step - -⚔ Learn about the [context environment](/context-environment). \ No newline at end of file diff --git a/articles/context-environment.md b/articles/context-environment.md deleted file mode 100644 index 7a8bc87d..00000000 --- a/articles/context-environment.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Context Environment -description: The environment object is in the framework store part of your context and gives you information about the current environment ---- - -The environment object is in the *framework store* part of your context and gives you information about the current environment. - -This key is *readonly* and available in both the *client* and *server* contexts. - -The following keys are available in the object: - -- *client*: boolean -- *server*: boolean -- *development*: boolean -- *production*: boolean -- *static*: boolean -- *key*: string - -```jsx -import Nullstack from 'nullstack'; - -class Page extends Nullstack { - - render({environment}) { - return ( -
- {environment.client &&

I am in the client

} - {environment.server &&

I am in the server

} - {environment.development &&

I am in development mode

} - {environment.production &&

I am in production mode

} - {environment.static &&

I am a static site

} -

My key is {environment.key}

-
- ) - } - -} - -export default Page; -``` - -The environment *key* is an md5 hash of the environment folder outputs that is appended to [assets](/styles) and [static API](/static-site-generation) path in order to assist cache control. - -## Next step - -⚔ Learn about the [context page](/context-page). \ No newline at end of file diff --git a/articles/context-page.md b/articles/context-page.md deleted file mode 100644 index 383e41cc..00000000 --- a/articles/context-page.md +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: Context Page -description: The page object is a proxy in the framework store part of your context and gives you information about the document head metatags ---- - -The page object is a proxy in the framework store part of your context and gives you information about the document head metatags. - -This key is *readwrite* and available only in the *client* context. - -Page keys will be used to generate metatags during [server-side rendering](/server-side-rendering) and must be assigned before [initiate](/full-stack-lifecycle) is resolved. - -The following keys are available in the object: - -- *title*: string -- *image*: string (absolute or relative URL) -- *description*: string -- *canonical*: string (absolute or relative URL) -- *locale*: string -- *robots*: string -- *schema*: object -- *changes*: string -- *priority*: number -- *status*: number - -When the title key is assigned on the client-side, the document title will be updated. - -Nullstack uses the *changes* and *priority* keys to generate the sitemap.xml. - -The sitemap is generated automatically only when using [static site generation](/static-site-generation) and must be manually generated in [server-side rendered](/server-side-rendering) applications - -The *changes* key represents the *changefreq* key in the sitemap.xml and if assigned must be one of the following values: - -- always -- hourly -- daily -- weekly -- monthly -- yearly -- never - -The *priority* key is a number between 0.0 and 1.0 that represents the *priority* key in the sitemap.xml. - -Nullstack does not set a default priority, however, sitemaps assume a 0.5 priority when not explicitly set. - -Besides *title* and *locale* all other keys have sensible defaults generated based on the application scope. - -```jsx -import Nullstack from 'nullstack'; - -class Page extends Nullstack { - - prepare({project, page}) { - page.title = `${project.name} - Page Title`; - page.image = '/image.jpg'; - page.description = 'Page meta description'; - page.canonical = 'http://absolute.url/canonical-link'; - page.locale = 'pt-BR'; - page.robots = 'index, follow'; - page.schema = {}; - page.changes = 'weekly'; - page.priority = 1; - } - - render({page}) { - return ( -
-

{page.title}

-

{page.description}

-
- ) - } - -} - -export default Page; -``` - -## Custom Events - -Updating *page.title* will raise a custom event. - -```jsx -import Nullstack from 'nullstack'; - -class Analytics extends Nullstack { - - hydrate({page}) { - window.addEventListener(page.event, () => { - console.log(page.title); - }); - } - -} - -export default Analytics; -``` - -## Error pages - -If during the [server-side render](/server-side-rendering) process the *page.status* has any value besides 200, your application will receive another render pass that gives you the chance to adjust the interface according to the status. - -The status key will be raised with the HTTP response. - -The page status will be modified to 500 and receive another render pass if the page raise an exception while rendering. - -The status of [server functions](/server-functions) responses will be set to the *page.status*. - -```jsx -import Nullstack from 'nullstack'; -import ErrorPage from './ErrorPage'; -import HomePage from './HomePage'; - -class Application extends Nullstack { - - // ... - - render({page}) { - return ( -
- {page.status !== 200 && } - -
- ) - } - -} - -export default Application; -``` - -> 🔥 Assigning to the status key during the [single-page application](/full-stack-lifecycle) mode will have no effect. - -## Next step - -⚔ Learn about the [context project](/context-project). \ No newline at end of file diff --git a/articles/context-project.md b/articles/context-project.md deleted file mode 100644 index 0d1ee278..00000000 --- a/articles/context-project.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -title: Context Project -description: The project object is a proxy in the framework store part of your context and gives you information about the app manifest and some metatags ---- - -The project object is a proxy in the framework store part of your context and gives you information about the app manifest and some metatags. - -This key is *readwrite* in the *server* context. - -This key is *readonly* in the *client* context. - -Project keys will be used to generate metatags during server-side rendering and must be assigned before [initiate](/full-stack-lifecycle) is resolved. - -Project keys will be used to generate the app manifest and should be set during the [application startup](/application-startup). - -The disallow key will be used to generate the robots.txt and should be set during the [application startup](/application-startup). - -Project keys are frozen after the [application startup](/application-startup). - -The following keys are available in the object: - -- *domain*: string -- *name*: string -- *shortName*: string -- *color*: string -- *backgroundColor*: string -- *type*: string -- *display*: string -- *orientation*: string -- *scope*: string -- *root*: string -- *icons*: object -- *favicon*: string (relative or absolute url) -- *disallow*: string array (relative paths) -- *sitemap*: boolean or string (relative or absolute url) -- *cdn*: string (absolute url) -- *protocol* string (http or https) - -Besides *domain*, *name* and *color* all other keys have sensible defaults generated based on the application scope. - -If you do not declare the *icons* key, Nullstack will scan any icons with the name following the pattern "icon-[WIDTH]x[HEIGHT].png" in your public folder. - -If the *sitemap* key is set to true your robots.txt file will point the sitemap to *https://${project.domain}/sitemap.xml*. - -The *cdn* key will prefix your asset bundles and will be available in the context so you can manually prefix other assets. - -The *protocol* key is "http" in development mode and "https" in production mode by default - -```jsx -import Nullstack from 'nullstack'; - -class Application extends Nullstack { - - static async start({project}) { - project.name = 'Nullstack'; - project.shortName = 'Nullstack'; - project.domain = 'nullstack.app'; - 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', - '128': '/icon-128x128.png', - '512': '/icon-512x512.png' - }; - project.favicon = '/favicon.png'; - project.disallow = ['/admin']; - project.sitemap = true; - project.cdn = 'cdn.nullstack.app'; - project.protocol = 'https'; - } - - prepare({project, page}) { - page.title = project.name; - } - -} - -export default Application; -``` - -> 💡 You can override the automatically generated manifest.json and robots.txt by serving your own file from the public folder - -## Next step - -⚔ Learn about the [context settings](/context-settings). \ No newline at end of file diff --git a/articles/context-secrets.md b/articles/context-secrets.md deleted file mode 100644 index 36954a4f..00000000 --- a/articles/context-secrets.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Context Secrets -description: The secrets object is a proxy in the framework store part of your context which you can use to configure your application with private information ---- - -The secrets object is a proxy in the framework store part of your context which you can use to configure your application with private information. - -This key is *readwrite* and available only in the *server* context. - -Secrets keys are frozen after the [application startup](/application-startup). - -The following keys are available in the object: - -- *development*: object -- *production*: object -- *[anySetting]*: any - -You can assign keys to *development* or *production* keys in order to have different secrets per [environment](/context-environment). - -If you assign a key directly to the secrets object it will be available in both environments. - -When reading from a key you must read directly from the secrets object and Nullstack will return the best-suited value for that [environment](/context-environment). - -```jsx -import Nullstack from 'nullstack'; - -class Application extends Nullstack { - - static async start({secrets}) { - secrets.development.privateKey = 'SANDBOX_API_KEY'; - secrets.production.privateKey = 'PRODUCTION_API_KEY'; - secrets.endpoint = 'https://domain.com/api'; - } - - static async fetchFromApi({secrets}) { - const response = await fetch(secrets.endpoint, { - headers: { - Authorization: `Bearer ${secrets.privateKey}` - } - }); - return await response.json(); - } - -} - -export default Application; -``` - -Any environment key starting with NULLSTACK_SECRETS_ will be mapped to the secrets in that environment. - -> 🐱‍💻 NULLSTACK_SECRETS_PRIVATE_KEY will be mapped to secrets.privateKey - -## Next step - -⚔ Learn about the [instance self](/instance-self). \ No newline at end of file diff --git a/articles/context-settings.md b/articles/context-settings.md deleted file mode 100644 index 922fe670..00000000 --- a/articles/context-settings.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: Context Settings -description: The settings object is a proxy in the framework store part of your context which you can use to configure your application with public information ---- - -The settings object is a proxy in the framework store part of your context which you can use to configure your application with public information. - -This key is *readwrite* in the *server* context. - -This key is *readonly* in the *client* context. - -Settings keys are frozen after the [application startup](/application-startup). - -The following keys are available in the object: - -- *development*: object -- *production*: object -- *[anySetting]*: any - -You can assign keys to *development* or *production* keys in order to have different settings per [environment](/context-environment). - -If you assign a key directly to the settings object it will be available in both environments. - -When reading from a key you must read directly from the settings object and Nullstack will return the best-suited value for that [environment](/context-environment). - -```jsx -import Nullstack from 'nullstack'; - -class Application extends Nullstack { - - static async start({settings}) { - settings.development.publicKey = 'SANDBOX_API_KEY'; - settings.production.publicKey = 'PRODUCTION_API_KEY'; - settings.endpoint = 'https://domain.com/api'; - } - - async hydrate({settings}) { - const response = await fetch(settings.endpoint, { - headers: { - Authorization: `Bearer ${settings.publicKey}` - } - }); - this.data = await response.json(); - } - -} - -export default Application; -``` - -Any environment key starting with NULLSTACK_SETTINGS_ will be mapped to the settings in that environment. - -> 🐱‍💻 NULLSTACK_SETTINGS_PUBLIC_KEY will be mapped to settings.publicKey - -## Next step - -⚔ Learn about the [context secrets](/context-secrets). \ No newline at end of file diff --git a/articles/getting-started.md b/articles/getting-started.md deleted file mode 100644 index 9c1f2864..00000000 --- a/articles/getting-started.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: Getting Started -description: Create full-stack javascript applications within seconds ---- - -> 📌 You can watch a video tutorial on our [Youtube Channel](https://www.youtube.com/watch?v=l23z00GEar8&list=PL5ylYELQy1hyFbguVaShp3XujjdVXLpId). - -Create full-stack javascript applications within seconds using *npx* to generate your project files from the latest template. - -> 🔥 The minimum required [node.js](https://nodejs.org) version for development mode is *12.12.0*. - -Replace *project-name* with your project name and run the command below to start a project: - -```sh -npx create-nullstack-app project-name -``` - -Change directory to the generated folder: - -```sh -cd project-name -``` - -Install the dependencies: - -```sh -npm install -``` - -Start the application in development mode: - -```sh -npm start -``` - -## Understanding the generated files - -The following folders and files will be generated: - -### index.js - -This is the [Webpack](https://webpack.js.org) entry point. - -Usually, you don't have to touch this file, but it is a convenient place to import global dependencies like CSS frameworks. - -### src/ - -This folder will contain the actual source code of your application. - -### src/Application.njs - -This is your application main file. - ->✨ Learn more about the [njs file extension](/njs-file-extension "Nullstack Javascript"). - -The start function will be automatically called once when you run *npm start*, use it to populate your server [context](/context) with things like [database](/how-to-use-mongodb-with-nullstack), [settings](/context-settings), and [secrets](/context-secrets). - ->✨ Learn more about the [application startup](/application-startup). - -### src/Application.scss - -This is an empty file just to demonstrate that you can use [SCSS with nullstack](/styles). - -It is a good practice to import a style file in a component with the same name. - ->✨ Learn more about [styles](/styles). - -### public/ - -Every file in here will be available to anyone from the domain root. - -By default *create-nullstack-app* generates the icons required for your manifest.json and images for OG meta tags. - ->✨ Learn more about [manifest.json](/context-project). - -Be sure to replace these images with your project identity. - -### .development/ - -This is the compiled result of your application in development mode. - -> 🔥 Do not touch this folder - -### .production/ - -This is the compiled result of your application in production mode. - -> 🔥 Do not touch this folder - ->✨ Learn more about [how to deploy a nullstack application](/how-to-deploy-a-nullstack-application). - -## Next step - -⚔ Create your first [renderable component](/renderable-components). \ No newline at end of file diff --git a/articles/how-to-use-mongodb-with-nullstack.md b/articles/how-to-use-mongodb-with-nullstack.md deleted file mode 100644 index 7a061abf..00000000 --- a/articles/how-to-use-mongodb-with-nullstack.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: How to use MongoDB -description: You can use any database with Nullstack, but the javascript integration and flexibility of MongoDB looks especially good with Nullstack applications ---- - -According to [mongodb.com](https://www.mongodb.com): - -"MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era." - -You can use any database with Nullstack, but the javascript integration and flexibility of MongoDB looks especially good with Nullstack applications. - -Install the MongoDB driver from npm: - -```sh -npm install mongodb -``` - -Configure the database credentials using [secrets](/context-secrets). - -The last step is to simply assign the database connection to the server context. - -```jsx -import Nullstack from 'nullstack'; -import {MongoClient} from 'mongodb'; - -class Application extends Nullstack { - - static async start(context) { - const {secrets} = context; - secrets.development.databaseHost = 'mongodb://localhost:27017/dbname'; - secrets.databaseName = 'dbname'; - await this.startDatabase(context); - } - - static async startDatabase(context) { - const {secrets} = context; - const databaseClient = new MongoClient(secrets.databaseHost); - await databaseClient.connect(); - context.database = await databaseClient.db(secrets.databaseName); - } - -} - -export default Application; -``` - -The example above will make the database key available to all your server functions. - -```jsx -import Nullstack from 'nullstack'; - -class BooksList extends Nullstack { - - books = []; - - static async getBooks({database}) { - return await database.collection('books').find().toArray(); - } - - async initiate() { - this.books = await this.getBooks(); - } - - // ... - -} - -export default BooksList; -``` - -## Next step - -⚔ Learn [how to use Google Analytics with Nullstack](/how-to-use-google-analytics-with-nullstack). \ No newline at end of file diff --git a/articles/instance-key.md b/articles/instance-key.md deleted file mode 100644 index 8719e86a..00000000 --- a/articles/instance-key.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -title: Instance Key -description: The instance key is a string in the framework store part of your context and allows you to persist the instance when it moves in the dom ---- - -The instance key is a string in the framework store part of your context and allows you to persist the instance when it moves in the dom. - -This key is *readonly* after you assign the attribute and available only in the *client* context. - -You can declare one key per instance. - -> 💡 If you do not declare a key nullstack will generate one based on dom depth. - -> 🔥 Keys cannot start with "_." to avoid conflicts with Nullstack generated keys - -Keys must be globally unique since the component could move anywhere around the dom and not only between its siblings. - -## Preserving state - -Keys are useful to preserve state in [stateful components](/stateful-components) when you move them in the dom. - -This is especially useful for dynamically sized lists that invoke components. - -```jsx -import Nullstack from 'nullstack'; -import Item from './Item'; - -class List extends Nullstack { - - // ... - - async initiate() { - this.items = await this.getItems(); - } - - render({self}) { - return ( - - ) - } - -} - -export default Page; -``` - -## Shared Instances - -You can also use keys to share the instance between two elements. - -Only the first encounter of the key will run its [lifecycle](/full-stack-lifecycle) - -```jsx -import Nullstack from 'nullstack'; - -class Counter extends Nullstack { - - count = 0; - - render({amount}) { - return ( -
- -
- ) - } - -} - -export default Counter; -``` - -```jsx -import Nullstack from 'nullstack'; -import Counter from './Counter'; - -class Application extends Nullstack { - - render() { - return ( -
- - - -
- ) - } - -} - -export default Application; -``` - -## Next step - -⚔ Learn about the [server request and response](/server-request-and-response). diff --git a/articles/instance-self.md b/articles/instance-self.md deleted file mode 100644 index d7ac2dc7..00000000 --- a/articles/instance-self.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: Instance Self -description: The self object is a proxy in the framework store part of your context and gives you information about the instance lifecycle ---- - -The self object is a proxy in the framework store part of your context and gives you information about the instance lifecycle. - -This key is *readonly* and available only in the *client* context. - -Each instance receives its own *self* object. - -The following keys are available in the object: - -- *initiated*: boolean -- *hydrated*: boolean -- *prerendered*: boolean -- *element*: HTMLElement - -When a lifecycle method is resolved, even if not declared, an equivalent key is set to true in self. - -If the component was server-side rendered the *prerendered* key will remain true until it is terminated. - -The *element* key points to the DOM selector and is only guaranteed to exist when hydrate is being called since prepare and initiate could run in the server. - -> 💡 Do not use *element* to guess the environment, instead use the [environment](/context-environment) for that. - -Observing self is a nice way to avoid giving placeholder information to the end-user. - -```jsx -import Nullstack from 'nullstack'; - -class Page extends Nullstack { - - // ... - - async initiate() { - this.price = await this.getPrice(); - } - - async hydrate({self}) { - self.element.querySelector('input').focus(); - } - - render({self}) { - if(!self.prerendered && !self.initiated) return false; - return ( -
- - -
- ) - } - -} - -export default Page; -``` - -> 💡 Components that get optimized into [functional components](/renderable-components) have no access to self. - -## Next step - -⚔ Learn about the [instance key](/instance-key). \ No newline at end of file diff --git a/articles/njs-file-extension.md b/articles/njs-file-extension.md deleted file mode 100644 index 46a8dee6..00000000 --- a/articles/njs-file-extension.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -title: NJS File Extension -description: Nullstack Javascript files let Webpack know which loaders to use at transpile time ---- - -Nullstack Javascript files let [Webpack](https://webpack.js.org) know which loaders to use at transpile time. - -NJS files must import Nullstack or one of its subclasses. - -If only a subclass is imported, a Nullstack import will be injected at transpile time. - -At transpile time JSX tags will be replaced with *Nullstack.element* - -This extension also allows Nullstack to make free transpile time optimizations like source injection and extracting renderable components into stateless functions. - -> 🔥 Each file must have only one class declaration. - -On the *server* bundle static async functions are mapped into a registry for security. - -On the *client* bundle static async functions are removed and replaced with a flag. - -On the *client* bundle static async functions with the name starting with "start" (and optionally followed by an uppercase letter) are completely removed. - -On both *server* and *client* bundles, a hash with the md5 of the original source code is added to the class. - -> 🐱‍💻 Bellow an example of a original .njs file. - -```jsx -import List from './List'; -import {readFileSync} from 'fs'; - -class Tasks extends List { - - static async getTasks({limit}) { - const json = readFileSync('tasks.json', 'utf-8'); - return JSON.parse(json).tasks.slice(0, limit); - } - - prepare(context) { - context.tasks = []; - } - - async initiate(context) { - context.tasks = await this.getTasks({limit: 10}); - } - - renderTask({task}) { - return ( -
  • - -
  • - ) - } - - render() { - return ( -
    - -
    - ) - } - -} - -export default Tasks; -``` - -> 🐱‍💻 Bellow an example of the same transpiled .njs file. - -```jsx -import Nullstack from 'nullstack'; -import List from './List'; - -class Tasks extends List { - - static hash = 'd493ac09d0d57574a30f136d31da455f'; - - static getTasks = true; - - prepare(context) { - context.tasks = []; - } - - async initiate(context) { - context.tasks = await this.getTasks({limit: 10}); - } - - renderTask({task}) { - return ( -
  • - -
  • - ) - } - - render() { - const Task = this.renderTask; - return ( -
    - -
    - ) - } - -} - -export default Tasks; -``` - -> 🐱‍💻 Bellow an example of a original .njs file. - -```jsx -import Nullstack from 'nullstack'; - -class Tasks extends Nullstack { - - renderTask({task}) { - return ( -
  • - -
  • - ) - } - - render({tasks}) { - return ( -
    - -
    - ) - } - -} - -export default Tasks; -``` - -> 🐱‍💻 Bellow an example of the same transpiled .njs file. - -```jsx -import Nullstack from 'nullstack'; - -class Tasks extends Nullstack { - - static renderTask({task}) { - return ( -
  • - -
  • - ) - } - - static render({tasks}) { - const Task = this.renderTask; - return ( -
    - -
    - ) - } - -} - -export default Tasks; -``` - -## Next step - -⚔ Learn about [server-side rendering](/server-side-rendering). \ No newline at end of file diff --git a/articles/renderable-components.md b/articles/renderable-components.md deleted file mode 100644 index 6f23990d..00000000 --- a/articles/renderable-components.md +++ /dev/null @@ -1,351 +0,0 @@ ---- -title: Renderable Components -description: Renderable components are very similar to web components they give you the ability to create new HTML tags that shortcut a group of other HTML tags ---- - -The simplest component you can make is a renderable component. - -Renderable components are very similar to web components, they give you the ability to create new HTML tags that shortcut a group of other HTML tags. - -Create a file in your src folder with the name of your component and the [njs extension](/njs-file-extension). - -In this example it is going to be called HelloWorld.njs. - -All you have to do is to import Nullstack or any of its subclasses and extend your class from it, define an instance method called render that returns any JSX, and export the component. - -> ✨ Install the official [Nullstack VSCode Extension](https://marketplace.visualstudio.com/items?itemName=ChristianMortaro.vscode-nullstack) to generate classes with a snippet. - -```jsx -import Nullstack from 'nullstack'; - -class HelloWorld extends Nullstack { - - render() { - return ( -
    Hello World
    - ) - } - -} - -export default HelloWorld; -``` - -The code above is just declaring the component, you still have to use it. - -Importing the component in your application gives you the ability to use a new tag in your render. - -This tag will be replaced with whatever you returned in your component render. - -```jsx -import Nullstack from 'nullstack'; - -import './Application.scss'; - -import HelloWorld from './HelloWorld'; - -class Application extends Nullstack { - - // ... - - render({page}) { - return ( -
    -

    {page.title}

    - Read the documentation - -
    - ) - } - -} - -export default Application; -``` - -> 💡 Components that do nothing besides rendering are extracted into faster functional components at transpile time! - -## Using HTML attributes - -Nullstack JSX deviates a little from the spec. - -You can use the normal HTML attributes like *class* and *for* directly. - -```jsx - -``` - -## Headless components - -If you want to skip rendering the component at all you can simply return false from the render. - -```jsx -import Nullstack from 'nullstack'; - -class Headless extends Nullstack { - - render() { - return false; - } - -} - -export default Headless; -``` - -This will allocate DOM space for when you decide to render markup there. - -This is also useful for conditional rendering. - -If all you want to do is to generate an invisible component you can skip defining the render method at all. - -## Inner components - -Instead of creating a new component just to organize code-splitting, you can create an inner component. - -Inner components are any method that the name starts with render followed by an uppercase character. - -Inner components share the same instance and scope as the main component, therefore, are very convenient to avoid problems like props drilling. - -To invoke the inner component use a JSX tag with the method name without the render prefix. - -```jsx -import Nullstack from 'nullstack'; - -class Post extends Nullstack { - - renderArticle() { - return ( -
    Content
    - ) - } - - renderAside() { - return ( - - ) - } - - render() { - return ( -
    -
    -
    - ) - } - -} - -export default HelloWorld; -``` - -> 💡 Nullstack will inject a constant reference to the function at transpile time in order to completely skip the runtime lookup process! - -## Boolean attributes - -Attributes can be assigned as a boolean. - -When the value is false the attribute will not be rendered at all. - -When the value is true it will be rendered as a boolean attribute without a string value. - -```jsx - -``` - -You can shortcut attributes when you know the value will always be true. - -```jsx - -``` - -> ✨ Learn more about [attributes](/context). - -## Element tag - -If you need to decide the tag name at runtime, you can use the element tag and set the tag attribute conditionally. - -```jsx - - some arbitrary text - -``` - -When the tag attribute is omitted, Nullstack will default to a *div*. - -## SVG Elements - -SVG can be used as if it were any regular HTML tag. - -You can manipulate the SVG using attributes and events normally. - -```jsx - - - -``` - -> ✨ Learn more about [events](/stateful-components). - -## Components with children - -Your component can be invoked passing a block of content. - -```jsx -
    -

    Hello World

    -
    -``` - -This doesn't automatically render the block since it wouldn't know where to place it. - -You can destructure the children on the render method and place it in your markup. - -```jsx -import Nullstack from 'nullstack'; - -class Header extends Nullstack { - - render({children}) { - return ( -
    {children}
    - ) - } - -} - -export default Header; -``` - -> ✨ This is possible because the children key is part of the [instance context](/context). - -## Lists - -You can map over lists without declaring a key. - -Lists that may change length must be wrapped in a parent element just for them. - -```jsx - -``` - -You can emulate a fixed-size list by returning false instead of an element to reserve dom space. - -```jsx -{list.map((item) => ( - item.visible ?
    {item.name}
    : false -)} -``` - -It's a nice practice to use inner components combined with lists to clean up your code. - -```jsx -import Nullstack from 'nullstack'; - -class List extends Nullstack { - - items = [ - {visible: true, number: 1}, - {visible: false, number: 2}, - {visible: true, number: 3} - ] - - renderItem({visible, number}) { - if(!visible) return false; - return ( -
  • {number}
  • - ) - } - - render() { - return ( - - ) - } - -} - -export default List; -``` - -> ✨ Sometimes you will notice keys in the map. Learn more about the [instance key](/instance-key). - -## Inner HTML - -You can set the inner HTML of an element with the *html* attribute. - -Links inside the HTML string will be replaced with [routable anchors](/routes-and-params). - -```jsx -import Nullstack from 'nullstack'; - -class Post extends Nullstack { - - content = ` -

    This is a Post

    - - Check this other post - - `; - - render() { - return ( -
    - ) - } - -} - -export default Post; -``` - -> 🔥 Be careful! When using user-generated HTML you are in risk of script injection - -## The head tag - -Renderable components can render inside the head tag an unlimited number of times at any depth of the application. - -The head tag will only be updated during the [server-side rendering](/server-side-rendering) process and changes will be ignored after the [hydration](/full-stack-lifecycle) process. - -```jsx -import Nullstack from 'nullstack'; - -class Application extends Nullstack { - - // ... - - render() { - return ( -
    -
    - - - -
    - - - - -
    - ) - } - -} - -export default Application; -``` - -> 🔥 you should not use the head tag to update [metatags](/context-page) that Nullstack already controls - -## Caveats - -Currently, Nullstack doesn't support JSX Fragments. If you want to see this feature implemented please [open an issue on github](https://github.com/nullstack/nullstack/issues). - -## Next step - -⚔ Add state to your component using [stateful components](/stateful-components). \ No newline at end of file diff --git a/articles/server-request-and-response.md b/articles/server-request-and-response.md deleted file mode 100644 index 8af1a389..00000000 --- a/articles/server-request-and-response.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -title: Server request and response -description: The server key is a proxy around the express instance that runs Nullstack under the hood ---- - -## The server key - -The server key is a proxy around the [Express](https://expressjs.com) instance that runs Nullstack under the hood. - -The server object is present only in the *server* context. - -The following functions are tunneled back to the express server: - -- get -- post -- put -- patch -- delete -- options -- head -- use - -> ✨ If you wanna know how to make an API with Nullstack, this is the way. - -```jsx -import Nullstack from 'nullstack'; - -class Application extends Nullstack { - - static async start({server}) { - server.get('/api/books', (request, response) => { - response.json({books: []}); - }); - } - - // ... - -} - -export default Application; -``` - -Other available keys are: - -- *port*: integer -- *maximumPayloadSize*: string -- *cors*: object - -```jsx -import Nullstack from 'nullstack'; - -class Application extends Nullstack { - - static async start({server}) { - server.port = 3000; - server.maximumPayloadSize = '5mb'; - server.cors = { - origin: 'http://localhost:6969', - optionsSuccessStatus: 200 - } - } - - // ... - -} - -export default Application; -``` - -The cors object will be passed as the argument to [express cors plugin](https://expressjs.com/en/resources/middleware/cors.html) - -## Request and Response - -Every server function context is merged with the original request and response objects from express. - -If you raise a response manually it will override the framework's [server-side rendering](/server-side-rendering) response. - -```jsx -import Nullstack from 'nullstack'; - -class Application extends Nullstack { - - static async getBooks({request, response}) { - if(!request.session.user) { - response.status(401).json({unauthorized: true}); - } - } - - // ... - -} - -export default Application; -``` - -## Next step - -⚔ Learn about [styles](/styles). \ No newline at end of file diff --git a/articles/stateful-components.md b/articles/stateful-components.md deleted file mode 100644 index 873cf9b5..00000000 --- a/articles/stateful-components.md +++ /dev/null @@ -1,167 +0,0 @@ ---- -title: Stateful Components -description: A productive full-stack web framework should not force you to think about framework details ---- - -A productive full-stack web framework should not force you to think about framework details. - -Nullstack takes control of its subclasses and generates a proxy for each instance. - -When you call anything on your class you are actually telling Nullstack what to do with the environment behind the scenes. - -This allows you to use vanilla javascript operations like assigning to a variable and see the reflection in the dom. - -## Mutability - -You can mutate instance variables to update your application state. - -Functions are automatically bound to the instance proxy and can be passed as a reference to events. - -Events are declared like normal HTML attributes. - -```jsx -import Nullstack from 'nullstack'; - -class Counter extends Nullstack { - - count = 0; - - increment() { - this.count++; - } - - render() { - return ( - - ) - } - -} - -export default Counter; -``` - -> 💡 Updates are made in batches, usually while awaiting async calls, so making multiple assignments have no performance costs! - -## Object Events - -You can shortcut events that are simple assignments by passing an object to the event. - -Each key of the object will be assigned to the instance. - -```jsx -import Nullstack from 'nullstack'; - -class Counter extends Nullstack { - - count = 0; - - render() { - return ( - - ) - } - -} - -export default Counter; -``` - -## Event Source - -By default, events refer to this when you pass an object. - -You can use the source attribute to define which object will receive the assignments. - -```jsx -import Nullstack from 'nullstack'; - -class Paginator extends Nullstack { - - render({params}) { - return ( - - ) - } - -} - -export default Paginator; -``` - -> ✨ Learn more about [context params](/routes-and-params). - -> 💡 If you do not declare a source to the event, Nullstack will inject a source={this} at transpile time in order to completely skip the runtime lookup process! - -## Event Context - -Attributes of the event target will be merged to the instance context and can be destructured in the function signature. - - -```jsx -import Nullstack from 'nullstack'; - -class Counter extends Nullstack { - - count = 0; - - increment({delta}) { - this.count += delta; - } - - render() { - return ( - - ) - } - -} - -export default Counter; -``` - -> 💡 Any attribute with primitive value will be added to the DOM. - -> ✨ Consider using [data attributes](/context-data) to make your html valid. - -## Original Event - -The browser default behavior is prevented by default. - -You can opt-out of this by declaring a default attribute to the event element. - -A reference to the original event is always merged with the function context. - -```jsx -import Nullstack from 'nullstack'; - -class Form extends Nullstack { - - submit({event}) { - event.preventDefault(); - } - - render() { - return ( -
    - -
    - ) - } - -} - -export default Form; -``` - -## Next steps - -⚔ Learn about the [full-stack lifecycle](/full-stack-lifecycle). \ No newline at end of file diff --git a/articles/styles.md b/articles/styles.md deleted file mode 100644 index 2cdce78f..00000000 --- a/articles/styles.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Styles -description: Using styles with Nullstack is as simple as importing a style file ---- - -Using styles with Nullstack is as simple as importing a style file. - -Nullstack comes with a [SASS](https://sass-lang.com) loader by default, but you can still use vanilla CSS. - -> ✨ It's a good practice to import a file with the same name as the component. - -```jsx -import Nullstack from 'nullstack'; -import './Header.scss'; - -class Header extends Nullstack { - // ... -} - -export default Header; -``` - -In production mode Nullstack uses [PurceCSS](https://purgecss.com), which cleans your client.css file, but has some gotchas. - -> ✨ Learn more about [safelisting your css](https://purgecss.com/safelisting.html) - -## Next step - -⚔ Learn about the [NJS file extension](/njs-file-extension). \ No newline at end of file diff --git a/client.js b/client.js new file mode 100644 index 00000000..e251a2a3 --- /dev/null +++ b/client.js @@ -0,0 +1,6 @@ +import Nullstack from "nullstack"; +import Application from "./src/Application"; + +const context = Nullstack.start(Application); + +export default context \ No newline at end of file diff --git a/docs/404.html b/docs/404.html deleted file mode 100644 index 8c1cdceb..00000000 --- a/docs/404.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - Not Found - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Page not Found

    - - - \ No newline at end of file diff --git a/docs/404/index.html b/docs/404/index.html deleted file mode 100644 index 8c1cdceb..00000000 --- a/docs/404/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - Not Found - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Page not Found

    - - - \ No newline at end of file diff --git a/docs/404/index.json b/docs/404/index.json deleted file mode 100644 index 8686fde4..00000000 --- a/docs/404/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"","html":""},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":404,"locale":"en","title":"Not Found - Nullstack","description":"Sorry, this is not the page you are looking for."}} \ No newline at end of file diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index e242231c..00000000 --- a/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -nullstack.app \ No newline at end of file diff --git a/docs/about/index.html b/docs/about/index.html deleted file mode 100644 index 2af9ddcb..00000000 --- a/docs/about/index.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - Why Nullstack Exists - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Why Nullstack Exists

    The sole purpose of Nullstack is to simplify the development by eliminating glue code and letting you focus on the logic of your application.

    -

    It was created keeping in mind programmers used to developing entire systems alone, but it is easily scalable to small or even big teams, as long as each programmer knows the flow of the feature they are to develop.

    -

    The Stack

    With the stack of technologies used in the web nowadays, the most common flow is something like this:

    -
      -
    • Front-end uses a reducer over a context that calls a fetcher;
    • -
    • This fetcher brings generic information over a RESTful API;
    • -
    • The RESTful API calls a server route, which calls a controller, which takes the information of a model and resolves it into a serializer;
    • -
    • If you need more than one resource, this process should be repeated until all resources are fetched;
    • -
    • After all the data has been fetched, only then should the front-end be able to use it;
    • -
    • Reason about how to deal with server-side render and hydration of the steps above;
    • -
    -

    Note that all you wanted was to show something from the database into a view. With Nullstack, that’s all you need to concern yourself with. Everything else is “glue code” and the framework should take care of it for you.

    -

    Feature-driven

    If you’re used to working on more than one project at a time or even if you just happen to have to give sporadic maintenance to a lot of your old projects, you might have stumbled upon this scenario: you don’t remember exactly where in your code is the logic you’re trying to fix or improve.

    -

    You might have a hook whose dependencies are local variables initialized with a redux state, which was stored at some point by an action declared somewhere in your source tree and called in who knows where.

    -

    If everything pertaining to a single feature were to be in the same file, maybe you wouldn’t need to reverse engineer your own code every time you need to update or fix something.

    -

    Putting everything in a single file may sound messy at a glance, but remember that you are the one who decides the granularity of this division.

    -

    A "feature" might be an entire register form or something as small as a button that does some verifications before letting you submit that form. It’s entirely up to you, and since each component is as complete as an entire feature, you could call this button or even the entire form on other pages in your application. This leads us to True Componentization and Code Reusability.

    -

    Componentization and Code Reusability

    Components in Nullstack are self-sufficient.

    -

    Most frameworks are specialized in a single layer, meaning that any component will be only as complete as its framework. When exporting a Nullstack component, all the code needed to run the feature is going to be together, without the need of allocating the other layers separately.

    -

    As a side effect, entire applications can be used as components, and mounted in other applications as engines.

    -

    Why object-oriented instead of functional

    At first glance, classes may look more verbose than the trendy functional components. -This section will explain the reasons that lead us to favor classes in the development of Nullstack.

    -

    The reasons are actually connected to some core principles of Nullstack, being:

    -

    Everything as Vanilla as Possible

    We didn’t want to introduce a “Nullstack way” of doing things and wanted it to be accessible to anyone with some Javascript knowledge.

    -

    That being said, the first big problem was to address state management in a vanilla Javascript way. Supporting functional components would require a solution similar to the hooks of React.js that would be considered a mannerism of the framework.

    -

    Since we opted out of immutability as a framework constraint, we are allowed to use the native way of setting simple variables. This removes the complexity of state management that created the need of third-party state management libraries in the first place.

    -

    No Glue Code or “Batteries Included”

    Nullstack borrows the concept of “battery-included” from Ember.js, but allows you to change batteries. Everything you need to make an application should be part of the framework, and still be flexible.

    -

    A framework should do the heavy lifting and a programmer should focus on his own application. -For this reason, all you have to do is to declare your classes and let Nullstack instantiate them for you. That way, we removed the most painful aspect of dealing with classes while maintaining all of the advantages of them.

    -

    Having a safe escape route

    Object-oriented versus functional is not a new topic, and lately the former seems to be bullied out of most frameworks, leaving no place for developers that enjoy this pattern.

    -

    Admittedly classes took too long to be standardized into Javascript and the delay might have caused some traumatic bad implementations along the way.

    -

    While object-oriented programming might not be the best solution for every problem, Nullstack allows you to import functions freely and use them in the moments when they should be the weapon of choice.

    -

    Why dependency injection instead of modularity

    Nullstack context uses the dependency injection pattern, which means that everything you need can be requested from the framework at the signature level of the function.

    -

    The context is a horizontally scoped object that is injected in all of your function calls. The non-hierarchical nature of this pattern allows you to easily move around your component's logic as your application grows, while still avoiding problems like props drilling or filling your view layer with store declarations.

    -

    This has two major advantages:

    -
      -
    • You see the dependencies of your code at a function level instead of having them all imported on top of the file.

    • -
    • The framework is able to give you the most precise information about the specific environment for that function call.

    • -
    -

    Developer Happiness

    The generated application is enough to have a PWA without thinking about boilerplates, but you are completely free to override the default behavior of each moving piece.

    -

    A borrowed concept from Ruby is developer happiness. Nullstack aims to ease the developer’s life by simplifying everything possible, but without hiding things from you.

    -

    The first developers we wanted to make happy are ourselves. We made Nullstack because we had fun in the process. It started as a simple prototype on top of React.js and we got carried away, each time making it more enjoyable for us until it became its own thing.

    -

    We hope you enjoy using Nullstack as much as we do because that's what keeps this project going forward.

    -
    - - - \ No newline at end of file diff --git a/docs/about/index.json b/docs/about/index.json deleted file mode 100644 index 5c9efb9f..00000000 --- a/docs/about/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Why Nullstack Exists","html":"

    The sole purpose of Nullstack is to simplify the development by eliminating glue code and letting you focus on the logic of your application.

    \n

    It was created keeping in mind programmers used to developing entire systems alone, but it is easily scalable to small or even big teams, as long as each programmer knows the flow of the feature they are to develop.

    \n

    The Stack

    With the stack of technologies used in the web nowadays, the most common flow is something like this:

    \n\n

    Note that all you wanted was to show something from the database into a view. With Nullstack, that’s all you need to concern yourself with. Everything else is “glue code” and the framework should take care of it for you.

    \n

    Feature-driven

    If you’re used to working on more than one project at a time or even if you just happen to have to give sporadic maintenance to a lot of your old projects, you might have stumbled upon this scenario: you don’t remember exactly where in your code is the logic you’re trying to fix or improve.

    \n

    You might have a hook whose dependencies are local variables initialized with a redux state, which was stored at some point by an action declared somewhere in your source tree and called in who knows where.

    \n

    If everything pertaining to a single feature were to be in the same file, maybe you wouldn’t need to reverse engineer your own code every time you need to update or fix something.

    \n

    Putting everything in a single file may sound messy at a glance, but remember that you are the one who decides the granularity of this division.

    \n

    A "feature" might be an entire register form or something as small as a button that does some verifications before letting you submit that form. It’s entirely up to you, and since each component is as complete as an entire feature, you could call this button or even the entire form on other pages in your application. This leads us to True Componentization and Code Reusability.

    \n

    Componentization and Code Reusability

    Components in Nullstack are self-sufficient.

    \n

    Most frameworks are specialized in a single layer, meaning that any component will be only as complete as its framework. When exporting a Nullstack component, all the code needed to run the feature is going to be together, without the need of allocating the other layers separately.

    \n

    As a side effect, entire applications can be used as components, and mounted in other applications as engines.

    \n

    Why object-oriented instead of functional

    At first glance, classes may look more verbose than the trendy functional components.\nThis section will explain the reasons that lead us to favor classes in the development of Nullstack.

    \n

    The reasons are actually connected to some core principles of Nullstack, being:

    \n

    Everything as Vanilla as Possible

    We didn’t want to introduce a “Nullstack way” of doing things and wanted it to be accessible to anyone with some Javascript knowledge.

    \n

    That being said, the first big problem was to address state management in a vanilla Javascript way. Supporting functional components would require a solution similar to the hooks of React.js that would be considered a mannerism of the framework.

    \n

    Since we opted out of immutability as a framework constraint, we are allowed to use the native way of setting simple variables. This removes the complexity of state management that created the need of third-party state management libraries in the first place.

    \n

    No Glue Code or “Batteries Included”

    Nullstack borrows the concept of “battery-included” from Ember.js, but allows you to change batteries. Everything you need to make an application should be part of the framework, and still be flexible.

    \n

    A framework should do the heavy lifting and a programmer should focus on his own application.\nFor this reason, all you have to do is to declare your classes and let Nullstack instantiate them for you. That way, we removed the most painful aspect of dealing with classes while maintaining all of the advantages of them.

    \n

    Having a safe escape route

    Object-oriented versus functional is not a new topic, and lately the former seems to be bullied out of most frameworks, leaving no place for developers that enjoy this pattern.

    \n

    Admittedly classes took too long to be standardized into Javascript and the delay might have caused some traumatic bad implementations along the way.

    \n

    While object-oriented programming might not be the best solution for every problem, Nullstack allows you to import functions freely and use them in the moments when they should be the weapon of choice.

    \n

    Why dependency injection instead of modularity

    Nullstack context uses the dependency injection pattern, which means that everything you need can be requested from the framework at the signature level of the function.

    \n

    The context is a horizontally scoped object that is injected in all of your function calls. The non-hierarchical nature of this pattern allows you to easily move around your component's logic as your application grows, while still avoiding problems like props drilling or filling your view layer with store declarations.

    \n

    This has two major advantages:

    \n\n

    Developer Happiness

    The generated application is enough to have a PWA without thinking about boilerplates, but you are completely free to override the default behavior of each moving piece.

    \n

    A borrowed concept from Ruby is developer happiness. Nullstack aims to ease the developer’s life by simplifying everything possible, but without hiding things from you.

    \n

    The first developers we wanted to make happy are ourselves. We made Nullstack because we had fun in the process. It started as a simple prototype on top of React.js and we got carried away, each time making it more enjoyable for us until it became its own thing.

    \n

    We hope you enjoy using Nullstack as much as we do because that's what keeps this project going forward.

    \n","description":"The sole purpose of Nullstack is to simplify the development by eliminating glue code and letting you focus on the logic of your application"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Why Nullstack Exists - Nullstack","description":"The sole purpose of Nullstack is to simplify the development by eliminating glue code and letting you focus on the logic of your application"}} \ No newline at end of file diff --git a/docs/application-startup/index.html b/docs/application-startup/index.html deleted file mode 100644 index d93be04e..00000000 --- a/docs/application-startup/index.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - Application Startup - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Application Startup

    The index.js file at your application root is responsible for starting your application.

    -

    When you run the application with npm start or node .production/server.js the index will call the start function in your src/Application.js.

    -

    The start function will run only once when your application is booted and is a good place for setting up your server context.

    -
    import Nullstack from 'nullstack';
    -import database from './database';
    -
    -class Application extends Nullstack {
    -
    -  static async start(context) {
    -    context.database = database;
    -  }
    -
    -}
    -
    -export default Application;
    -
    -

    Dependency startup pattern

    A nice pattern to work with dependencies that require startup time configurations is to define a start function in the dependency and call it in the Application start function passing the server context.

    -
    import Nullstack from 'nullstack';
    -import Dependency from './Dependency';
    -
    -class Application extends Nullstack {
    -
    -  static async start(context) {
    -    Dependency.start(context);
    -  }
    -
    -}
    -
    -export default Application;
    -
    -
    -

    🔒 Server functions with the name starting with "start" (and optionally followed by an uppercase letter) do not generate an API endpoint to avoid malicious context flooding.

    -
    -

    Next step

    ⚔ Learn about the context data.

    -
    - - - \ No newline at end of file diff --git a/docs/application-startup/index.json b/docs/application-startup/index.json deleted file mode 100644 index 4f57a497..00000000 --- a/docs/application-startup/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Application Startup","html":"

    The index.js file at your application root is responsible for starting your application.

    \n

    When you run the application with npm start or node .production/server.js the index will call the start function in your src/Application.js.

    \n

    The start function will run only once when your application is booted and is a good place for setting up your server context.

    \n
    import Nullstack from 'nullstack';\nimport database from './database';\n\nclass Application extends Nullstack {\n\n  static async start(context) {\n    context.database = database;\n  }\n\n}\n\nexport default Application;\n
    \n

    Dependency startup pattern

    A nice pattern to work with dependencies that require startup time configurations is to define a start function in the dependency and call it in the Application start function passing the server context.

    \n
    import Nullstack from 'nullstack';\nimport Dependency from './Dependency';\n\nclass Application extends Nullstack {\n\n  static async start(context) {\n    Dependency.start(context);\n  }\n\n}\n\nexport default Application;\n
    \n
    \n

    🔒 Server functions with the name starting with "start" (and optionally followed by an uppercase letter) do not generate an API endpoint to avoid malicious context flooding.

    \n
    \n

    Next step

    ⚔ Learn about the context data.

    \n","description":"The start function will run only once when your application is booted and is a good place for setting up your server context"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Application Startup - Nullstack","description":"The start function will run only once when your application is booted and is a good place for setting up your server context"}} \ No newline at end of file diff --git a/docs/client-f0cee0769c64977a287dddeaae84c7f6.css b/docs/client-f0cee0769c64977a287dddeaae84c7f6.css deleted file mode 100644 index e6d5dd2a..00000000 --- a/docs/client-f0cee0769c64977a287dddeaae84c7f6.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{margin:0.67em 0}a{background-color:transparent}b,strong{font-weight:bolder}img{border-style:none}[type="submit"]{-webkit-appearance:button}[type="submit"]::-moz-focus-inner{border-style:none;padding:0}[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}blockquote,h1,h2,h3,h4,figure,p,pre{margin:0}ul{list-style:none;margin:0;padding:0}html{line-height:1.2}*,::before,::after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e2e8f0}img{border-style:solid}a{color:inherit;text-decoration:inherit}img,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}body *{box-sizing:border-box}:root{--spacer: 0.25rem}@media (min-width: 0px){.m1l{margin-left:calc(var(--spacer) * 1)}.m1y{margin-top:calc(var(--spacer) * 1)}.m1b,.m1y{margin-bottom:calc(var(--spacer) * 1)}.m2x{margin-left:calc(var(--spacer) * 2)}.m2x{margin-right:calc(var(--spacer) * 2)}.m2t{margin-top:calc(var(--spacer) * 2)}.m2b{margin-bottom:calc(var(--spacer) * 2)}.m3y{margin-top:calc(var(--spacer) * 3)}.m3b,.m3y{margin-bottom:calc(var(--spacer) * 3)}.m4t,.m4y{margin-top:calc(var(--spacer) * 4)}.m4b,.m4y{margin-bottom:calc(var(--spacer) * 4)}.m5t{margin-top:calc(var(--spacer) * 5)}.m6y{margin-top:calc(var(--spacer) * 6)}.m6b,.m6y{margin-bottom:calc(var(--spacer) * 6)}.m8t{margin-top:calc(var(--spacer) * 8)}.m8b{margin-bottom:calc(var(--spacer) * 8)}.m10y{margin-top:calc(var(--spacer) * 10)}.m10b,.m10y{margin-bottom:calc(var(--spacer) * 10)}.m12b{margin-bottom:calc(var(--spacer) * 12)}.m14t{margin-top:calc(var(--spacer) * 14)}.m20t{margin-top:calc(var(--spacer) * 20)}.p1{padding:calc(var(--spacer) * 1)}.p1l,.p1x{padding-left:calc(var(--spacer) * 1)}.p1x{padding-right:calc(var(--spacer) * 1)}.p1y{padding-top:calc(var(--spacer) * 1)}.p1b,.p1y{padding-bottom:calc(var(--spacer) * 1)}.p2{padding:calc(var(--spacer) * 2)}.p2y{padding-top:calc(var(--spacer) * 2)}.p2y{padding-bottom:calc(var(--spacer) * 2)}.p3y{padding-top:calc(var(--spacer) * 3)}.p3y{padding-bottom:calc(var(--spacer) * 3)}.p4{padding:calc(var(--spacer) * 4)}.p4x{padding-left:calc(var(--spacer) * 4)}.p4x{padding-right:calc(var(--spacer) * 4)}.p4t,.p4y{padding-top:calc(var(--spacer) * 4)}.p4y{padding-bottom:calc(var(--spacer) * 4)}.p8y{padding-top:calc(var(--spacer) * 8)}.p8y{padding-bottom:calc(var(--spacer) * 8)}.p10t,.p10y{padding-top:calc(var(--spacer) * 10)}.p10y{padding-bottom:calc(var(--spacer) * 10)}.p20y{padding-top:calc(var(--spacer) * 20)}.p20y{padding-bottom:calc(var(--spacer) * 20)}}@media (max-width: 768px){.sm\-m1y{margin-top:calc(var(--spacer) * 1)}.sm\-m1y{margin-bottom:calc(var(--spacer) * 1)}.sm\-m3t{margin-top:calc(var(--spacer) * 3)}.sm\-m10t{margin-top:calc(var(--spacer) * 10)}.sm\-p2l,.sm\-p2x{padding-left:calc(var(--spacer) * 2)}.sm\-p2x{padding-right:calc(var(--spacer) * 2)}.sm\-p4{padding:calc(var(--spacer) * 4)}.sm\-p4x{padding-left:calc(var(--spacer) * 4)}.sm\-p4x{padding-right:calc(var(--spacer) * 4)}.sm\-p4t{padding-top:calc(var(--spacer) * 4)}.sm\-p10t,.sm\-p10y{padding-top:calc(var(--spacer) * 10)}.sm\-p10y{padding-bottom:calc(var(--spacer) * 10)}}@media (max-width: 992px){.md\-p2x{padding-left:calc(var(--spacer) * 2)}.md\-p2x{padding-right:calc(var(--spacer) * 2)}}@media (min-width: 768px){.md\+m2x{margin-left:calc(var(--spacer) * 2)}.md\+m2x{margin-right:calc(var(--spacer) * 2)}.md\+m20t{margin-top:calc(var(--spacer) * 20)}.md\+p3l{padding-left:calc(var(--spacer) * 3)}.md\+p10l{padding-left:calc(var(--spacer) * 10)}.md\+p10y{padding-top:calc(var(--spacer) * 10)}.md\+p10b,.md\+p10y{padding-bottom:calc(var(--spacer) * 10)}.md\+p20t,.md\+p20y{padding-top:calc(var(--spacer) * 20)}.md\+p20y{padding-bottom:calc(var(--spacer) * 20)}}@media (max-width: 1200px){.lg\-p2x{padding-left:calc(var(--spacer) * 2)}.lg\-p2x{padding-right:calc(var(--spacer) * 2)}}@media (min-width: 0px){.pftl{position:fixed;left:0;top:0}.pabl{position:absolute;left:0;bottom:0}.prtl{position:relative;left:0;top:0}}:root{--box-shadow: 2px}@media (min-width: 0px){.bs2{box-shadow:0 0px calc(2 * var(--box-shadow)) 0 rgba(0,0,0,0.2)}}@media (min-width: 0px){.z24{z-index:2400}}@media (min-width: 0px){.off{display:none !important}}@media (max-width: 768px){.sm\-off{display:none !important}}@media (min-width: 768px){.md\+off{display:none !important}}.x{width:100%}@media (min-width: 992px){.x{max-width:960px;margin-left:auto;margin-right:auto}}@media (min-width: 1200px){.x{max-width:1140px;margin-left:auto;margin-right:auto}}@media (min-width: 0px){.xl{display:flex;flex-wrap:wrap;justify-content:flex-start;text-align:left}.xx{display:flex;flex-wrap:wrap;justify-content:center;text-align:center}.xr{display:flex;flex-wrap:wrap;justify-content:flex-end;text-align:right}.xsb{display:flex;flex-wrap:wrap;justify-content:space-between}}@media (min-width: 0px){.yy{display:flex;flex-wrap:wrap;align-content:center}}@media (max-width: 768px){.sm\-xr{display:flex;flex-wrap:wrap;justify-content:flex-end;text-align:right}.sm\-xsb{display:flex;flex-wrap:wrap;justify-content:space-between}}:root{--x-columns: 12;--y-columns: 12;--y-height: 100%;--x-width: 100%}@media (min-width: 0px){.xvw{--x-width: 100vw}.x8{width:calc((var(--x-width) / var(--x-columns)) * 8) !important}.x12{width:calc((var(--x-width) / var(--x-columns)) * 12) !important}.yvh{--y-height: 100vh}.y12{height:calc((var(--y-height) / var(--y-columns)) * 12) !important}}@media (max-width: 768px){.sm\-x4{width:calc((var(--x-width) / var(--x-columns)) * 4) !important}.sm\-x12{width:calc((var(--x-width) / var(--x-columns)) * 12) !important}}@media (max-width: 992px){.md\-x12{width:calc((var(--x-width) / var(--x-columns)) * 12) !important}}@media (min-width: 768px){.md\+x4{width:calc((var(--x-width) / var(--x-columns)) * 4) !important}.md\+x6{width:calc((var(--x-width) / var(--x-columns)) * 6) !important}.md\+x10{width:calc((var(--x-width) / var(--x-columns)) * 10) !important}}@media (max-width: 1200px){.lg\-x12z{max-width:calc((var(--x-width) / var(--x-columns)) * 12) !important}}@media (min-width: 992px){.lg\+x6{width:calc((var(--x-width) / var(--x-columns)) * 6) !important}}:root{--font-size: 0.25rem}@media (min-width: 0px){.fs4{font-size:calc(4 * var(--font-size))}.fs6{font-size:calc(6 * var(--font-size))}}@media (max-width: 768px){.sm\-fs6{font-size:calc(6 * var(--font-size))}.sm\-fs8{font-size:calc(8 * var(--font-size))}}@media (min-width: 768px){.md\+fs8{font-size:calc(8 * var(--font-size))}.md\+fs12{font-size:calc(12 * var(--font-size))}}:root{--primary-font-family: 'Arial';--secondary-font-family: 'Tahoma';--tertiary-font-family: 'Tahoma'}@media (min-width: 0px){.ff2{font-family:var(--secondary-font-family)}}@media (min-width: 0px){.fw3{font-weight:300}}:root{--line-height: 0.1}@media (min-width: 0px){.lh12{line-height:calc(12 * var(--line-height))}.lh16{line-height:calc(16 * var(--line-height))}}:root{--primary-color: #d22365;--primary-light-color: #ff94bd;--primary-dark-color: #530020;--secondary-color:#6b46c1;--secondary-light-color: #ceb9ff;--secondary-dark-color: #311e6d;--tertiary-color:#2ee9b4;--tertiary-light-color: #a4ffe5;--tertiary-dark-color:#004d37;--success-color: #75c38f;--success-light-color:#e5f7ec;--success-dark-color:#315542;--warning-color: #ffc400;--warning-light-color: #fbf6b4;--warning-dark-color: #663d1d;--danger-color:#ff3838;--danger-light-color: #f7e7e6;--danger-dark-color: #950000;--soft-color: #fff;--soft-light-color: transparent;--soft-dark-color: #e6e6e6;--neutral-color: #b3b3b3;--neutral-light-color: #e7e7e7;--neutral-dark-color: #999999;--heavy-color: #282c34;--heavy-light-color: #575f70;--heavy-dark-color:#0d0f11}@media (min-width: 0px){.bci1{border-color:var(--primary-color);border-width:1px;border-style:solid}.bcm2{border-color:var(--neutral-color);border-width:1px;border-style:solid}.bcm2t{border-top-color:var(--neutral-color);border-top-width:1px;border-top-style:solid}.bgi1{background-color:var(--primary-color)}.bgs2{background-color:var(--warning-color)}.bgm1{background-color:var(--soft-color)}.bgm2{background-color:var(--neutral-color)}.bgm3{background-color:var(--heavy-color)}.ci1{color:var(--primary-color)}.cm1{color:var(--soft-color)}.cm2z{color:var(--neutral-dark-color)}.cm3{color:var(--heavy-color)}.bgm1\:h:hover{background-color:var(--soft-color)}.ci1\:h:hover{color:var(--primary-color)}}@media (max-width: 768px){.sm\-bcm2t{border-top-color:var(--neutral-color);border-top-width:1px;border-top-style:solid}.sm\-bcm2b{border-bottom-color:var(--neutral-color);border-bottom-width:1px;border-bottom-style:solid}}@media (min-width: 768px){.md\+bci1{border-color:var(--primary-color);border-width:1px;border-style:solid}.md\+bcm2t,.md\+bcm2y{border-top-color:var(--neutral-color);border-top-width:1px;border-top-style:solid}.md\+bcm2y{border-bottom-color:var(--neutral-color);border-bottom-width:1px;border-bottom-style:solid}.md\+bgi1\:h:hover{background-color:var(--primary-color)}.md\+cm1\:h:hover{color:var(--soft-color)}}:root{--border-radius: 0.25rem}:root{--opacity: 0.05}@media (min-width: 0px){.op18{opacity:calc(18 * var(--opacity))}}:root{--letter-spacing: 0.1px}@media (min-width: 0px){.ls12{letter-spacing:calc(12 * var(--letter-spacing))}} - -@font-face{font-family:'Roboto';font-style:normal;font-weight:300;src:local(""),url("/roboto-v20-latin-300.woff2") format("woff2");font-display:swap}@font-face{font-family:'Roboto';font-style:normal;font-weight:500;src:local(""),url("/roboto-v20-latin-500.woff2") format("woff2");font-display:swap}@font-face{font-family:'Crete Round';font-style:normal;font-weight:400;src:local(""),url("/crete-round-v9-latin-regular.woff2") format("woff2");font-display:swap}:root{--primary-font-family: 'Crete Round', serif;--secondary-font-family: 'Roboto', sans-serif;--primary-color: #d22365;--soft-color: #fff;--neutral-color: #f0f0f2;--heavy-color: #282c34;--warning-color: #ffffd6}body{font-family:var(--secondary-font-family);font-weight:300;color:var(--heavy-color);padding-top:70px;overflow-y:scroll}h1,h2,h3,h4{font-family:var(--primary-font-family);font-weight:700;letter-spacing:1px}strong{font-weight:700}blockquote{line-height:140%}section a img{transition:.1s}section a img:hover{transform:scale(1.1);z-index:1;transition:.3s}pre{color:#ddbc72;font-size:1.2rem;width:100%;background-color:#282c34;line-height:1.5;padding:0.5rem;color:#ddbc72}@media (max-width: 768px){pre{overflow-x:auto}}.token.selector,.token.tag{color:#ff5d9a !important}.token.comment{color:#9dbcf7 !important}.token.string,.token.attr-value{color:#99c47a !important}.token.atrule,.token.keyword{color:#e89bff !important}.token.property,.token.boolean,.token.number,.token.constant,.token.symbol,.token.attr-name,.token.deleted{color:#e9ac72 !important}.token.function,.token.operator{color:#71bfff !important}.token.punctuation{color:#b1b8c6 !important}article :target::before{content:'';display:block;height:calc(80px + 1rem);margin-top:calc(-80px + -1rem)}article h2{margin-top:2rem}article p,article h2,article h3,article h4,article pre,article blockquote,article ul{margin-bottom:1rem}article ul{list-style-type:circle;padding-left:1.3rem}article li{padding:0.35rem 0}article a{color:var(--primary-color)}article h2 a,article h3 a{color:var(--heavy-color)}article em{font-weight:500;font-style:normal}article blockquote{border:1px solid var(--neutral-color);padding:0.5rem;margin-left:0}article blockquote p{margin-bottom:0}@media (max-width: 768px){article pre code.language-jsx{overflow-x:scroll}} - diff --git a/docs/client-f0cee0769c64977a287dddeaae84c7f6.js b/docs/client-f0cee0769c64977a287dddeaae84c7f6.js deleted file mode 100644 index 0231a68b..00000000 --- a/docs/client-f0cee0769c64977a287dddeaae84c7f6.js +++ /dev/null @@ -1 +0,0 @@ -!function(e){var t={};function __webpack_require__(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,__webpack_require__),r.l=!0,r.exports}__webpack_require__.m=e,__webpack_require__.c=t,__webpack_require__.d=function(e,t,n){__webpack_require__.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},__webpack_require__.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.t=function(e,t){if(1&t&&(e=__webpack_require__(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(__webpack_require__.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)__webpack_require__.d(n,r,function(t){return e[t]}.bind(null,r));return n},__webpack_require__.n=function(e){var t=e&&e.__esModule?function getDefault(){return e.default}:function getModuleExports(){return e};return __webpack_require__.d(t,"a",t),t},__webpack_require__.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},__webpack_require__.p="",__webpack_require__(__webpack_require__.s=3)}([function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){"use strict";n.r(t);n(0);const r=/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/,i=/^\/Date\((d|-|.*)\)[\/|\\]$/;function dateParser(e,t){if("string"==typeof t){let e=r.exec(t);if(e)return new Date(t);if(e=i.exec(t),e){const t=e[1].split(/[-+,.]/);return new Date(t[0]?+t[0]:0-+t[1])}}return t}function deserialize(e){return JSON.parse(e,dateParser)}function element_element(e,t={},...n){return null===t&&(t={}),n=function flattenChildren(e){return e=[].concat.apply([],e).map(e=>null!=e&&e),[].concat.apply([],e)}(n),"textarea"===e&&(n=[n.join("")]),"element"===e&&(e=t.tag||"div",delete t.tag),t.children=n,"function"==typeof e&&void 0!==e.render?{type:e,attributes:t,children:null}:{type:e,attributes:t,children:n}}function extractParamValue(e){return"true"===e||"false"!==e&&(e?decodeURIComponent(e.replace(/\+/g," ")):"")}function serializeParam(e){return e&&void 0!==e.toJSON?e.toJSON():e}function serializeSearch(e){return Object.keys(e).map(t=>!1===e[t]||e[t]?`${t}=${e[t]}`:"").filter(e=>!!e).join("&")}const s={};var a=s;const l={set(e,t,n){const r=serializeParam(n);e[t]=r;const i=serializeSearch(e);return N.url=N.path+(i?"?":"")+i,!0},get:(e,t)=>!1!==e[t]&&(!1!==a[t]&&(e[t]||a[t]||""))},o={...window.params};delete window.params;const c=new Proxy(o,l);function updateParams(e){!function resetSegments(){for(const e in s)delete s[e]}();const t=function getQueryStringParams(e){const[t,n]=e.split("?");return n?n.split("&").reduce((e,t)=>{let[n,r]=t.split("=");return e[n]=extractParamValue(r),e},{}):{}}(e);for(const e of Object.keys({...t,...o}))o[e]=t[e];return c}var u=c;const d={...window.environment,client:!0,server:!1};delete window.environment,Object.freeze(d);var m=d;function extractLocation(e){let[t,n]=e.split("#"),[r,i]=t.split("?");"/"!==r&&r.endsWith("/")&&(r=r.substring(0,r.length-1));let s=r;i&&(s+="?"+i);let a=s;return n&&(a+="#"+n),void 0===n&&(n=""),{path:r,search:i,url:s,urlWithHash:a,hash:n}}function isFalse(e){return null==e||!1===e||!1===e.type}function isClass(e){return"function"==typeof e.type&&"function"==typeof e.type.prototype.render}function isFunction(e){return"function"==typeof e.type&&void 0===e.type.prototype}function isStatic(e){return"function"==typeof e.type&&("function"==typeof e.type.render||e.type.name&&!e.type.prototype)}function isText(e){return void 0===e.children}const p=deserialize(JSON.stringify(window.context));delete window.context;const h={set(e,t,n){return p[t]=n,y.update(),Reflect.set(...arguments)},get:(e,t)=>void 0===e[t]?p[t]:e[t]};function generateContext(e){return new Proxy(e,h)}var f=p;function generateKey(e){return"_."+e.join(".")}function routableNode(e,t){if(function isRoutable(e){return e&&void 0!==e.attributes&&void 0!==e.attributes.route}(e)){const n=t.slice(0,-1).join(".");if(void 0!==y.routes[n])e.type=!1,e.children=[];else{const t=function routeMatches(e,t){let{path:n}=extractLocation(e);const r=n.split("/"),i=t.split("/"),s={},a=Math.max(r.length,i.length);let l=!1;for(let e=0;e{const{event:s,value:a}=n;"checked"==r?t[e.attributes.bind]=s.target[r]:!0===t[e.attributes.bind]||!1===t[e.attributes.bind]?t[e.attributes.bind]=s?"true"==s.target[r]:a:"number"==typeof t[e.attributes.bind]?t[e.attributes.bind]=parseFloat(s?s.target[r]:a)||0:t[e.attributes.bind]=s?s.target[r]:a,y.update(),void 0!==i&&setTimeout(()=>{i({...e.attributes,...n})},0)}}}function anchorableNode(e){if(function isAnchorable(e){return"a"===e.type&&e.attributes.href&&e.attributes.href.startsWith("/")&&!e.attributes.target}(e)){const t=e.attributes.onclick;e.attributes.onclick=({event:n})=>{n.preventDefault(),N.url=e.attributes.href,t&&setTimeout(()=>{t({...e.attributes,event:n})},0)}}}function anchorableElement(e){const t=e.querySelectorAll('a[href^="/"]:not([target])');for(const e of t)e.onclick=t=>{t.preventDefault(),N.url=e.getAttribute("href")}}function parameterizableNode_anchorableNode(e,t,n){if(function isParameterizable(e){return e&&e.attributes&&(e.attributes.params||e.attributes.path)}(e)){let r;if(e.attributes.params){r={};for(const t in e.attributes.params)r[t]=serializeParam(e.attributes.params[t])}else r=n;const i=serializeSearch(r),s=e.attributes.path||t.path;e.attributes.href=s+(i?"?":"")+i,delete e.attributes.path,delete e.attributes.params}}function camelize(e){return e.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g,(e,t)=>t.toUpperCase())}function kebabize(e){return e.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,"$1-$2").toLowerCase()}function datableNode(e){if(e&&e.attributes){e.attributes.data=e.attributes.data||{};for(const t in e.attributes)if(t.startsWith("data-")){const n=camelize(t.slice(5));e.attributes.data[n]=e.attributes[t]}for(const t in e.attributes.data){const n="data-"+kebabize(t);e.attributes[n]=e.attributes.data[t]}}}function objectEvent(e){for(const t in e.attributes)if(t.startsWith("on")&&"object"==typeof e.attributes[t]){const n=e.attributes.source,r=e.attributes[t];e.attributes[t]=function(){for(const e in r)n[e]=r[e];y.update()}.bind(n)}}function render(e,t){if(routableNode(e,t),datableNode(e),isFalse(e)||"head"===e.type)return document.createComment("");if(objectEvent(e),bindableNode(e),isStatic(e)){const n=(e.type.render||e.type).call(e.type,{...f,...e.attributes});return e.children=[n],render(e.children[0],[...t,0])}if(isFunction(e)){const n=e.type(e.attributes);return e.children=[n],render(e.children[0],[...t,0])}if(isClass(e)){const n=e.attributes.key||generateKey([0,...t]),r=y.instances[n]||new e.type,i=window.instances[n];if(i){for(const e in i)r[e]=i[e];delete window.instances[n],r._self.initiated=!0,r._self.prerendered=!0}r._events={},r._attributes=e.attributes;const s=generateContext(e.attributes);r._context=s,i||null!=y.instances[n]||(y.initiationQueue.push(r),r.prepare&&r.prepare()),y.instances[n]=r;const a=r.render();return e.children=[a],y.renewalQueue.push(r),r._self.element=render(e.children[0],[...t,0]),y.hydrationQueue.push(r),r._self.element}if(isText(e))return document.createTextNode(e);let n,r=y.nextVirtualDom,i=!1;for(const e of t){if(!r.children)break;if(r=r.children[e],!r)break;if("svg"===r.type){i=!0;break}}n=i?document.createElementNS("http://www.w3.org/2000/svg",e.type):document.createElement(e.type),parameterizableNode_anchorableNode(e,N,u),anchorableNode(e);for(let r in e.attributes)if("html"===r)n.innerHTML=e.attributes[r],anchorableElement(n);else if(r.startsWith("on")){const i=r.replace("on",""),s=generateKey(t)+"."+i;y.events[s]=t=>{!0!==e.attributes.default&&t.preventDefault(),e.attributes[r]({...e.attributes,event:t})},n.addEventListener(i,y.events[s])}else{const t=typeof e.attributes[r];"object"!==t&&"function"!==t&&("value"!=r&&!0===e.attributes[r]?n.setAttribute(r,""):("value"==r||!1!==e.attributes[r]&&null!==e.attributes[r]&&void 0!==e.attributes[r])&&n.setAttribute(r,e.attributes[r]))}if(!e.attributes.html){for(let r=0;r{!0!==s.attributes.default&&e.preventDefault(),s.attributes[t]({...s.attributes,event:e})},a.addEventListener(e,y.events[r])):delete y.events[r]}else{const e=typeof s.attributes[t];"object"!==e&&"function"!==e&&(void 0!==i.attributes[t]&&void 0===s.attributes[t]?a.removeAttribute(t):i.attributes[t]!==s.attributes[t]&&("value"!=t&&!1===s.attributes[t]||null===s.attributes[t]||void 0===s.attributes[t]?a.removeAttribute(t):"value"!=t&&!0===s.attributes[t]?a.setAttribute(t,""):a.setAttribute(t,s.attributes[t])))}if(s.attributes.html)return;const r=Math.max(i.children.length,s.children.length);for(const e of s.children)routableNode(e,[...t,0]);if(s.children.length>i.children.length){for(let e=0;es.children.length){for(let e=0;e=s.children.length;e--)a.removeChild(a.childNodes[e])}else for(let e=r-1;e>-1;e--)rerender(a,[...t,e],[...n,e]);"textarea"==s.type&&(a.value=s.children.join("")),"select"==s.type&&(a.value=s.attributes.value)}}}}const b={initialized:!1,hydrated:!1,initializer:null,instances:{},initiationQueue:[],renewalQueue:[],hydrationQueue:[],virtualDom:{},selector:null,routes:{},events:{},renderQueue:null,update:function(){b.initialized&&(clearInterval(b.renderQueue),b.renderQueue=setTimeout(()=>{b.initialized=!1,b.routes={},b.initiationQueue=[],b.renewalQueue=[],b.hydrationQueue=[],b.nextVirtualDom=b.initializer(),rerender(b.selector,[0],[]),b.virtualDom=b.nextVirtualDom,b.nextVirtualDom=null,b.processLifecycleQueues()},16))},processLifecycleQueues:async function(){b.initialized||(b.initialized=!0,b.hydrated=!0);const e=b.initiationQueue,t=b.hydrationQueue;for(const t of e)t.initiate&&await t.initiate(),t._self.initiated=!0;e.length&&b.update();for(const e of t)e.hydrate&&await e.hydrate(),e._self.hydrated=!0;t.length&&b.update();for(const e in b.instances){const t=b.instances[e];b.renewalQueue.includes(t)||(t.terminate&&await t.terminate(),delete b.instances[e])}N._changed=!1}};var y=b;var g=new Proxy({},{set:(e,t,n)=>(e[t]=n,y.update(),!0),get:(e,t)=>e[t]||!1});const x={...window.worker};x.online=navigator.onLine,delete window.worker,x.loading=g;const w=new Proxy(x,{set:(e,t,n)=>(e[t]!==n&&(e[t]=n,y.update()),!0)});if(x.enabled){window.addEventListener("beforeinstallprompt",(function(e){e.preventDefault(),w.installation=e})),async function register(){if("serviceWorker"in navigator){const e=`/service-worker-${m.key}.js`;try{w.registration=await navigator.serviceWorker.register(e,{scope:"/"})}catch(e){console.log(e)}}}()}window.addEventListener("online",()=>{w.online=!0,m.static?N._update(N.url):w.responsive=!0}),window.addEventListener("offline",()=>{w.online=!1});var v=w;function windowEvent(e){clearTimeout(null),setTimeout(()=>{const t=new Event("nullstack."+e);window.dispatchEvent(t)},0)}const k={...window.page,event:"nullstack.page"};delete window.page;var _=new Proxy(k,{set(e,t,n){"title"===t&&(document.title=n);const r=Reflect.set(...arguments);return"title"===t&&windowEvent("page"),y.update(),r}});function _defineProperty(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}let C=null;var N=new class router_Router{constructor(){_defineProperty(this,"event","nullstack.router"),_defineProperty(this,"_changed",!1);const{hash:e,url:t}=extractLocation(window.location.pathname+window.location.search);this._url=t,this._hash=e}async _popState(){const{urlWithHash:e}=extractLocation(window.location.pathname+window.location.search);await this._update(e,!1)}async _update(e,t){const{url:n,path:r,hash:i,urlWithHash:s}=extractLocation(e);clearTimeout(C),C=setTimeout(async()=>{if(_.status=200,m.static){v.fetching=!0;const e="/index.json",t="/"===r?e:r+e;try{const e=await fetch(t),r=await e.json(n);window.instances=r.instances;for(const e in r.page)_[e]=r.page[e];v.responsive=!0}catch(e){v.responsive=!1}v.fetching=!1}t&&history.pushState({},document.title,s),this._url=n,this._hash=i,this._changed=!0,updateParams(n),y.update(),windowEvent("router")},0)}async _redirect(e){const{url:t,hash:n,urlWithHash:r}=extractLocation(e);t===this._url&&this._hash===n||await this._update(r,!0),n||window.scroll(0,0)}get url(){return this._url}set url(e){this._redirect(e)}get path(){return extractLocation(this._url).path}set path(e){this._redirect(e+window.location.search)}};var P={get(e,t){return void 0===e[t]&&!0===e.constructor[t]?async n=>{let r;v.fetching=!0,v.loading[t]=!0;const i=`/nullstack/${e.constructor.hash}/${t}.json`;try{const e=await fetch(i,{method:"POST",headers:v.headers,mode:"cors",cache:"no-cache",credentials:"same-origin",redirect:"follow",referrerPolicy:"no-referrer",body:JSON.stringify(n||{})});_.status=e.status;r=deserialize(await e.text()).result,v.responsive=!0}catch(e){v.responsive=!1}return v.fetching=!1,delete v.loading[t],r}:"function"==typeof e[t]?n=>{const r=generateContext({...e._context,...n,self:e._self});return e[t](r)}:Reflect.get(...arguments)},set(e,t,n){const r=Reflect.set(...arguments);return t.startsWith("_")||y.update(),r}};const j={...window.settings};delete window.settings,Object.freeze(j);var A=j;const S={...window.project};delete window.project,Object.freeze(S);var T=S;function client_defineProperty(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}f.page=_,f.router=N,f.settings=A,f.worker=v,f.params=u,f.project=T;class client_Nullstack{static start(e){window.instances=deserialize(JSON.stringify(window.instances)),window.addEventListener("popstate",()=>{N._popState()}),y.routes={},updateParams(N.url),y.currentInstance=null,y.initializer=()=>element_element(e),y.selector=document.querySelector("#application"),y.virtualDom=y.initializer(),f.environment=m,y.nextVirtualDom=y.initializer(),rerender(y.selector,[0],[]),y.virtualDom=y.nextVirtualDom,y.nextVirtualDom=null,y.processLifecycleQueues()}constructor(){client_defineProperty(this,"_self",{prerendered:!1,initiated:!1,hydrated:!1});const e=Object.getOwnPropertyNames(Object.getPrototypeOf(this)),t=new Proxy(this,P);for(const n of e)"constructor"!==n&&"function"==typeof this[n]&&(this[n]=this[n].bind(t));return t}render(){return!1}}client_defineProperty(client_Nullstack,"element",element_element);var L=client_Nullstack;n(1);var z=class Documentation_Documentation extends L{prepare({project:e,page:t}){t.title="Documentation - "+e.name,t.description="Follow these steps and become a full-stack javascript developer!",t.priority=.8}renderLink({title:e}){const t="/"+e.toLowerCase().split(" ").join("-");return L.element("a",{href:t,class:"xl x12 p3y bcm2t ci1"}," ",e," ")}renderTopic({title:e,description:t,children:n}){return L.element("div",{class:"x12 m6y bcm2 p4x p4t p1b"},L.element("h2",{class:"x12 sm-fs6 md+fs8 m2b"}," ",e," "),L.element("p",{class:"x12 fs4 m6b"}," ",t," "),L.element("nav",{class:"x12"}," ",n," "))}render(){const e=this.renderLink,t=this.renderTopic;return L.element("section",{class:"x sm-p4x sm-p10y md+p20y"},L.element("h1",{class:"x12 sm-fs6 md+fs12 m2b"}," Nullstack Documentation "),L.element("p",{class:"x12 fs4"}," Follow these steps and become a full-stack javascript developer! "),L.element(t,{title:"Core concepts",description:"Start your journey in Nullstack with these basic concepts"},L.element(e,{title:"Getting started"}),L.element(e,{title:"Renderable components"}),L.element(e,{title:"Stateful components"}),L.element(e,{title:"Full-stack lifecycle"}),L.element(e,{title:"Server functions"}),L.element(e,{title:"Context"}),L.element(e,{title:"Routes and params"}),L.element(e,{title:"Two-way bindings"})),L.element(t,{title:"Advanced concepts",description:"These are concepts that you will most likely learn as you need in your projects"},L.element(e,{title:"Application Startup"}),L.element(e,{title:"Context data"}),L.element(e,{title:"Context environment"}),L.element(e,{title:"Context page"}),L.element(e,{title:"Context project"}),L.element(e,{title:"Context settings"}),L.element(e,{title:"Context secrets"}),L.element(e,{title:"Instance self"}),L.element(e,{title:"Instance Key"}),L.element(e,{title:"Server request and response"}),L.element(e,{title:"Styles"}),L.element(e,{title:"NJS file extension"}),L.element(e,{title:"Server-side rendering"}),L.element(e,{title:"Static site generation"}),L.element(e,{title:"Service Worker"}),L.element(e,{title:"How to deploy a Nullstack application"})),L.element(t,{title:"Examples",description:"The best way to learn Nullstack is by reading some code"},L.element(e,{title:"How to use MongoDB with Nullstack"}),L.element(e,{title:"How to use Google Analytics with Nullstack"}),L.element(e,{title:"How to use Facebook Pixel with Nullstack"})))}};var D=class Components_Documentation extends L{prepare({project:e,page:t}){t.title="Community Components - "+e.name,t.description="A curated list of Nullstack components made by the community",t.priority=.3}renderProject({title:e,repository:t}){return L.element("a",{href:t,target:"_blank",rel:"noopener",class:"xl x12 p3y bcm2t ci1"},e)}renderTopic({title:e,children:t}){return L.element("div",{class:"x12 m6y bcm2 p4x p4t p1b"},L.element("h2",{class:"x12 sm-fs6 md+fs8 m3b"}," ",e," "),L.element("nav",{class:"x12"}," ",t," "))}render(){const e=this.renderProject,t=this.renderTopic;return L.element("section",{class:"x sm-p4x sm-p10y md+p20y"},L.element("h1",{class:"x12 sm-fs6 md+fs12 m2b"}," Community Components "),L.element("p",{class:"x12 fs4"}," A curated list of Nullstack components made by the community. "),L.element("p",{class:"m2t"},"If you want to add a component to this list",L.element("a",{href:"https://github.com/nullstack/nullstack.github.io/issues",class:"ci1"}," open an issue on github "),"."),L.element(t,{title:"Integrations"},L.element(e,{title:"Google Analytics",repository:"https://github.com/Mortaro/nullstack-google-analytics"}),L.element(e,{title:"Facebook Pixel",repository:"https://github.com/Mortaro/nullstack-facebook-pixel"})),L.element(t,{title:"General Inputs"},L.element(e,{title:"Currency Input",repository:"https://github.com/Mortaro/nullstack-currency-input"}),L.element(e,{title:"Date Input",repository:"https://github.com/Mortaro/nullstack-date-input"}),L.element(e,{title:"CKEditor Adapter",repository:"https://github.com/Mortaro/nullstack-ckeditor-adapter"})),L.element(t,{title:"Brazilian Inputs"},L.element("p",{class:"bgs2 p2 m3y",title:"Nullstack is a Brazilian Framework"}," 🤘 Nullstack é BR porr@! "),L.element(e,{title:"CPF and CNPJ Inputs",repository:"https://github.com/Mortaro/nullstack-cpf-cnpj-input"}),L.element(e,{title:"CEP Input",repository:"https://github.com/Mortaro/nullstack-cep-input"}),L.element(e,{title:"Phone Input",repository:"https://github.com/Mortaro/nullstack-phone-input"})))}};n(2);function Article_defineProperty(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}class Article_Component extends L{constructor(...e){super(...e),Article_defineProperty(this,"title",""),Article_defineProperty(this,"html","")}async initiate({project:e,page:t,params:n}){const r=await this.getArticleByKey({key:n.slug});r.title?(Object.assign(this,r),t.title=`${r.title} - ${e.name}`,t.description=r.description):(t.status=404,t.title="Not Found - "+e.name,t.description="Sorry, this is not the page you are looking for.")}renderArticle(){return L.element("section",{class:"x sm-p4x sm-p10y md+p20y"},L.element("h1",{class:"x12 sm-fs6 md+fs8 m6b"}," ",this.title," "),L.element("article",{html:this.html}))}renderNotFound(){return L.element("section",{class:"x sm-p4x sm-p10y md+p20y"},L.element("h1",{class:"x12 sm-fs6 md+fs8 m6b"}," Page not Found "),L.element("article",null,L.element("p",null,"Perhaps you want to learn about",L.element("a",{href:"/context-page",class:"m1l"},"how to make a 404 page with Nullstack"),"?"),L.element("p",null,"If you are looking for something else, you should",L.element("a",{href:"/documentation",class:"m1l"},"read the documentation"),".")))}render({page:e}){const t=this.renderArticle,n=this.renderNotFound;return 404==e.status?L.element(n,null):L.element(t,null)}}Article_defineProperty(Article_Component,"hash","e5842c0339f14e33f9de8399223251f9"),Article_defineProperty(Article_Component,"getArticleByKey",!0);var F=Article_Component,stroke=({width:e,height:t,length:n,title:r,rotation:i,animation:s,speed:a,class:l,color:o="currentColor"})=>{const c=!!i&&`rotate(${i})`,u={slow:"1.5s",fast:"0.5s"}[a]||"1.0s";return L.element("svg",{width:e,height:t,transform:c,class:l,viewBox:"0 0 512 512"},r&&L.element("title",null,r),"spin"===s&&L.element("animateTransform",{attributeType:"xml",attributeName:"transform",type:"rotate",from:"360 0 0",to:"0 0 0",dur:u,additive:"sum",repeatCount:"indefinite"}),L.element("rect",{x:"10",y:"10",width:"492",height:"90.4043",rx:"17.383","stroke-width":n||20,stroke:o,"stroke-linecap":"round","stroke-linejoin":"round",fill:"none"}),L.element("rect",{x:"10",y:"411.5957",width:"492",height:"90.4043",rx:"17.383","stroke-width":n||20,stroke:o,"stroke-linecap":"round","stroke-linejoin":"round",fill:"none"}),L.element("rect",{x:"10",y:"210.7979",width:"492",height:"90.4043",rx:"17.383","stroke-width":n||20,stroke:o,"stroke-linecap":"round","stroke-linejoin":"round",fill:"none"}))},ex_stroke=({width:e,height:t,length:n,title:r,rotation:i,animation:s,speed:a,class:l,color:o="currentColor"})=>{const c=!!i&&`rotate(${i})`,u={slow:"1.5s",fast:"0.5s"}[a]||"1.0s";return L.element("svg",{width:e,height:t,transform:c,class:l,viewBox:"0 0 512 512"},r&&L.element("title",null,r),"spin"===s&&L.element("animateTransform",{attributeType:"xml",attributeName:"transform",type:"rotate",from:"360 0 0",to:"0 0 0",dur:u,additive:"sum",repeatCount:"indefinite"}),L.element("path",{d:"M335.1385,256,495.697,95.4415a21.52,21.52,0,0,0,0-30.4336L446.9921,16.303a21.52,21.52,0,0,0-30.4336,0L256,176.8615,95.4415,16.303a21.52,21.52,0,0,0-30.4336,0L16.303,65.0079a21.52,21.52,0,0,0,0,30.4336L176.8615,256,16.303,416.5585a21.52,21.52,0,0,0,0,30.4336L65.0079,495.697a21.52,21.52,0,0,0,30.4336,0L256,335.1385,416.5585,495.697a21.52,21.52,0,0,0,30.4336,0l48.7049-48.7049a21.52,21.52,0,0,0,0-30.4336Z",transform:"translate(0 0)",fill:"none",stroke:o,"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":n||20}))};var O=class Header_Header extends L{constructor(...e){super(...e),function Header_defineProperty(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}(this,"expanded",!1)}renderLink({title:e,href:t,target:n}){return L.element("a",{href:t,target:n,source:this,onclick:!n&&{expanded:!1},rel:!!n&&"noopener",class:"sm-x12 sm-bcm2b p2 ci1:h"}," ",e," ")}render(){const e=this.renderLink;return L.element("header",{class:"x12 pftl bgm1 bs2"},L.element("div",{class:"x xsb yy p4y"},L.element("div",{class:"sm-x12 sm-xsb sm-p4x yy"},L.element("a",{href:"/",title:"Nullstack"},L.element("img",{src:"/nullstack.svg",alt:"Nullstack",width:"135",height:"30"})),L.element("span",{source:this,onclick:{expanded:!this.expanded},class:"yy md+off"},L.element("element",{tag:this.expanded?ex_stroke:stroke,height:20,class:"cm3"}))),L.element("nav",{class:"yy sm-p4 "+(!this.expanded&&"sm-off")},L.element(e,{title:"About",href:"/about"}),L.element(e,{title:"Documentation",href:"/documentation"}),L.element(e,{title:"Components",href:"/components"}),L.element(e,{title:"Contributors",href:"/contributors"}),L.element(e,{title:"Source",href:"https://github.com/nullstack/nullstack",target:"_blank"})),L.element("div",{class:"sm-x12 sm-p4x "+(!this.expanded&&"sm-off")},L.element("a",{href:"/getting-started",source:this,onclick:{expanded:!1},class:"xx sm-x12 bci1 bgi1 bgm1:h cm1 ci1:h p4x p2y"},"Get Started"))))}};var E=class Footer_Footer extends L{renderLink({title:e,href:t}){return L.element("a",{href:t,rel:"noopener",target:"_blank",class:"sm-xr sm-m1y sm-x12 md+bci1 sm-bcm2t sm-p4t ci1 md+cm1:h md+bgi1:h p4x p2y md+m2x"},e)}render(){const e=this.renderLink;return L.element("footer",{class:"xx m20t"},L.element("div",{class:"x xr md+bcm2t yy md+p10y prtl"},L.element("a",{href:"/waifu"},L.element("img",{src:"/nullachan.png",alt:"Nulla-Chan",title:"Nulla-Chan: Nullstack's official waifu",class:"pabl sm-p2l",height:"160",loading:"lazy"})),L.element("nav",{class:"xr sm-x4 yy"},L.element(e,{title:"YouTube",href:"https://www.youtube.com/channel/UCUNPaxoppH3lu6JTrUX78Ww"}),L.element(e,{title:"Twitter",href:"https://twitter.com/nullstackapp"}),L.element(e,{title:"GitHub",href:"https://github.com/nullstack"}))))}};function Snippet_defineProperty(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}class Snippet_Snippet extends L{constructor(...e){super(...e),Snippet_defineProperty(this,"html","")}async initiate({key:e}){this.html=await this.getSnippetByKey({key:e})}render(){return L.element("pre",{class:"bgm3 p4"},L.element("code",{html:this.html}))}}Snippet_defineProperty(Snippet_Snippet,"hash","ccfb5ab375885b78c747ca3d8ff0be00"),Snippet_defineProperty(Snippet_Snippet,"getSnippetByKey",!0);var M=Snippet_Snippet,cog_stroke=({width:e,height:t,length:n,title:r,rotation:i,animation:s,speed:a,class:l,color:o="currentColor"})=>{const c=!!i&&`rotate(${i})`,u={slow:"1.5s",fast:"0.5s"}[a]||"1.0s";return L.element("svg",{width:e,height:t,transform:c,class:l,viewBox:"0 0 512 512"},r&&L.element("title",null,r),"spin"===s&&L.element("animateTransform",{attributeType:"xml",attributeName:"transform",type:"rotate",from:"360 0 0",to:"0 0 0",dur:u,additive:"sum",repeatCount:"indefinite"}),L.element("path",{d:"M501.9984,279.0708a16.0691,16.0691,0,0,1-13.4278,15.8986l-35.9968,5.9988A17.6011,17.6011,0,0,0,438.58,313.2018a190.8568,190.8568,0,0,1-13.0446,31.4643A17.5918,17.5918,0,0,0,426.82,363.09l21.2127,29.7891a16.0776,16.0776,0,0,1-1.7514,20.7457l-32.6594,32.6583a16.0432,16.0432,0,0,1-20.72,1.77l-29.6617-21.2007a17.59,17.59,0,0,0-18.5045-1.2931,192.38,192.38,0,0,1-31.5078,13.0457,17.6007,17.6007,0,0,0-12.1433,13.9121l-6.1141,36.0359a16.07,16.07,0,0,1-15.903,13.4484H232.82a16.0653,16.0653,0,0,1-15.8975-13.4277l-6-35.9967a17.6049,17.6049,0,0,0-12.1182-13.9633,191.0666,191.0666,0,0,1-30.5031-12.54,17.62,17.62,0,0,0-18.399,1.2888L119.709,448.852a15.4291,15.4291,0,0,1-9.2011,2.9509A15.7635,15.7635,0,0,1,99.1461,447.05l-32.7182-32.715a15.9459,15.9459,0,0,1-1.7394-20.626l21.0854-29.3308a17.5751,17.5751,0,0,0,1.255-18.6569,186.7013,186.7013,0,0,1-13.2112-31.3456A17.637,17.637,0,0,0,59.935,302.2907l-36.49-6.2208A16.0752,16.0752,0,0,1,10,280.168V234.0254a16.0757,16.0757,0,0,1,13.4571-15.902l35.5549-6a17.5854,17.5854,0,0,0,13.9763-12.2794,188.24,188.24,0,0,1,12.828-31.5448,17.6135,17.6135,0,0,0-1.2877-18.3989L63.09,119.78A16.0645,16.0645,0,0,1,64.82,99.0512l32.77-32.6584.1187-.124a15.6331,15.6331,0,0,1,11.2594-4.6469,15.8133,15.8133,0,0,1,9.3122,2.951l29.457,21.2039a17.5634,17.5634,0,0,0,18.6536,1.2507,186.6559,186.6559,0,0,1,31.3326-13.2068,17.6346,17.6346,0,0,0,12.0921-13.887l6.2252-36.49A16.0692,16.0692,0,0,1,231.9433,9.9987h46.1426a16.0748,16.0748,0,0,1,15.902,13.4571l6,35.5678a17.5926,17.5926,0,0,0,12.2968,13.9721,189.9907,189.9907,0,0,1,32.6888,13.4528,17.612,17.612,0,0,0,18.45-1.2714L393.28,63.9132a15.41,15.41,0,0,1,9.2011-2.9509,15.7748,15.7748,0,0,1,11.3618,4.7546l32.7138,32.7138a15.95,15.95,0,0,1,1.7144,20.6608L427.05,148.7826a17.6069,17.6069,0,0,0-1.2714,18.48,192.2247,192.2247,0,0,1,13.049,31.511,17.6,17.6,0,0,0,13.9121,12.139l36.0055,6.11.03.0043A15.6605,15.6605,0,0,1,501.9984,232.7Z",transform:"translate(0 0.0013)",fill:"none",stroke:o,"stroke-miterlimit":"10","stroke-width":n||20}),L.element("circle",{cx:"256",cy:"256.0013",r:"101",fill:"none",stroke:o,"stroke-miterlimit":"10","stroke-width":n||20}))},heartbeat_stroke=({width:e,height:t,length:n,title:r,rotation:i,animation:s,speed:a,class:l,color:o="currentColor"})=>{const c=!!i&&`rotate(${i})`,u={slow:"1.5s",fast:"0.5s"}[a]||"1.0s";return L.element("svg",{width:e,height:t,transform:c,class:l,viewBox:"0 0 512 512"},r&&L.element("title",null,r),"spin"===s&&L.element("animateTransform",{attributeType:"xml",attributeName:"transform",type:"rotate",from:"360 0 0",to:"0 0 0",dur:u,additive:"sum",repeatCount:"indefinite"}),L.element("path",{d:"M256.0491,165.8235c-6.0229-62.1643-55.5458-111.6161-113.0353-111.6161-61.5719,0-111.4858,55.5181-111.4858,124.0033,0,38.8968,16.1059,73.6046,41.2994,96.34l-.2914-.001L254.7788,457.7926,436.7424,275.829h-.0008c26.0153-22.7008,42.73-57.9881,42.73-97.6183,0-68.4852-49.9139-124.0033-111.4858-124.0033-57.8133,0-106.3487,48.947-111.9368,111.6161",transform:"translate(0 0)",fill:"none",stroke:o,"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":n||20}),L.element("polyline",{points:"10 271.077 129.954 271.077 160.643 160.216 187.204 243.836 241.858 243.836 268.929 323.667 323.583 160.216 354.72 271.035 502 271.035",fill:"none",stroke:o,"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":n||20}))},qrcode_stroke=({width:e,height:t,length:n,title:r,rotation:i,animation:s,speed:a,class:l,color:o="currentColor"})=>{const c=!!i&&`rotate(${i})`,u={slow:"1.5s",fast:"0.5s"}[a]||"1.0s";return L.element("svg",{width:e,height:t,transform:c,class:l,viewBox:"0 0 512 512"},r&&L.element("title",null,r),"spin"===s&&L.element("animateTransform",{attributeType:"xml",attributeName:"transform",type:"rotate",from:"360 0 0",to:"0 0 0",dur:u,additive:"sum",repeatCount:"indefinite"}),L.element("rect",{x:"74.1597",y:"73.5849",width:"151.0896",height:"151.2926",rx:"10.435","stroke-width":n||20,stroke:o,"stroke-linecap":"round","stroke-linejoin":"round",fill:"none"}),L.element("rect",{x:"286.7507",y:"73.5849",width:"151.0896",height:"151.2926",rx:"11.935","stroke-width":n||20,stroke:o,"stroke-linecap":"round","stroke-linejoin":"round",fill:"none"}),L.element("rect",{x:"74.1597",y:"286.4615",width:"151.0896",height:"151.2926",rx:"13.935","stroke-width":n||20,stroke:o,"stroke-linecap":"round","stroke-linejoin":"round",fill:"none"}),L.element("rect",{x:"284.7951",y:"284.5033",width:"85.677",height:"85.7921",rx:"7.935","stroke-width":n||20,stroke:o,"stroke-linecap":"round","stroke-linejoin":"round",fill:"none"}),L.element("rect",{x:"372.2824",y:"372.1081",width:"61.792",height:"61.875",rx:"1.6716","stroke-width":n||20,stroke:o,"stroke-linecap":"round","stroke-linejoin":"round",fill:"none"}),L.element("path",{d:"M124.4721,10.0426h-79.04S10,7.3135,10,45.5221v80.511",transform:"translate(0 0)",fill:"none",stroke:o,"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":n||20}),L.element("path",{d:"M10.0426,387.3742v79.1464S7.3171,502,45.4744,502h80.403",transform:"translate(0 0)",fill:"none",stroke:o,"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":n||20}),L.element("path",{d:"M501.9574,124.6258V45.4794S504.6829,10,466.5256,10h-80.403",transform:"translate(0 0)",fill:"none",stroke:o,"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":n||20}),L.element("path",{d:"M387.5279,501.9574h79.04S502,504.6865,502,466.4779v-80.511",transform:"translate(0 0)",fill:"none",stroke:o,"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":n||20}))};var I=class Home_Component extends L{prepare({project:e,page:t}){t.title=e.name+" - Full-stack Javascript Components",t.description="Nullstack is a full-stack javascript framework for building progressive web applications",t.priority=1}renderHero(){return L.element("section",{class:"x xx sm-p2x p20y"},L.element("h1",{class:"x12 sm-fs8 md+fs12"}," Full-stack Javascript Components "),L.element("div",{class:"xx x8 m12b",style:"background-image: linear-gradient(0deg, #fff 49%, #e2e8f0 50%, #fff 52%);"},L.element("p",{class:"bgm1 fs6 p2"}," for one-dev armies ")),L.element("div",null,L.element("p",{class:"x12 fs4 lh12 ls12"}," Nullstack is a full-stack framework for building ",L.element("strong",null,"progressive web applications"),". "),L.element("p",{class:"x12 fs4 lh12 ls12"}," It connects a ",L.element("strong",null," stateful UI ")," layer to specialized ",L.element("strong",null,"microservices")," in the same component using ",L.element("strong",null,"vanilla javascript.")," "),L.element("p",{class:"x12 fs4 lh12 ls12 m4t"}," Focus on solving your business logic instead of writing glue code. ")))}renderFeature({title:e,key:t,link:n}){return L.element("div",{class:"md-x12 lg+x6 p1"},!!e&&L.element("div",{class:"xsb bcm2 p4"},L.element("h3",{class:"ff2 fw3 fs4"},L.element("a",{href:n,class:"ci1"},e))),L.element(M,{key:t}))}renderShowcase(){const e=this.renderFeature;return L.element("section",{class:"x lg-x12z xl md-p2x"},L.element(e,{key:"Application"}),L.element(e,{key:"TaskList"}),L.element("div",{class:"xl x12 p1"},L.element("div",{class:"xl x12 bcm2"},L.element("p",{class:"xx x12 m5t p4x lh16"},"The example above uses",L.element("a",{href:"/server-functions",class:"ci1 p1x"}," server functions "),"to read tasks from a JSON file and store them in the",L.element("a",{href:"/context",class:"ci1 p1x"}," context "),"available to all components."),L.element("p",{class:"xx x12 m6b p4x lh16"},"The tasks are listed in a specific",L.element("a",{href:"/routes-and-params",class:"ci1 p1x"}," route "),"that renders a component with multiple",L.element("a",{href:"/renderable-components",class:"ci1 p1x"}," inner components "),"filtered by status with inputs using",L.element("a",{href:"/two-way-bindings",class:"ci1 p1l"}," two-way bindings "),"."))))}renderAbout(){return L.element("section",{class:"x xx sm-p2x sm-p10y md+p20t md+p10b"},L.element("h2",{class:"x12 sm-fs8 md+fs12 m2b"}," Complete Features as Components "),L.element("p",{class:"x12 fs4"}," Nullstack is not another part of your stack, it is your stack "),L.element("p",{class:"x12 fs4"}," Your application can be exported from back-end to front-end as a component and mounted into another application "))}renderStep({title:e,children:t,link:n,icon:r}){return L.element("div",{class:"md+x4 p1"},L.element("div",{class:"xx bgm2 p8y p4x y12"},L.element(r,{height:40,class:"cm2z m4b"}),L.element("h2",{class:"x12 fs6"},L.element("a",{href:n,class:"ci1"},e)),L.element("p",{class:"x12 fs4 m4y"}," ",t," ")))}renderCycle(){const e=this.renderStep;return L.element("section",{class:"x xx sm-p2x md+bcm2y md+p10y"},L.element(e,{icon:cog_stroke,title:"Server-Side Rendering",link:"/server-side-rendering"},"Nullstack generates ",L.element("strong",null," SEO ready ")," HTML optimized for the first paint of your route in a single request using local functions with ",L.element("strong",null," zero javascript ")," dependencies in the client bundle."),L.element(e,{icon:heartbeat_stroke,title:"Single Page Application",link:"/full-stack-lifecycle"},"After hydration, requests will fetch JSON from an ",L.element("strong",null," automatically generated API "),"off server functions, update the application state, and rerender the page."),L.element(e,{icon:qrcode_stroke,title:"Static Site Generation",link:"/static-site-generation"},"You can even use Nullstack to generate lightning-fast ",L.element("strong",null," static websites "),"that serve HTML and become a single page application using an automatically generated ",L.element("strong",null," static API "),"."))}renderProductivity(){return L.element("section",{class:"x xx sm-p2x sm-p10y md+p20y"},L.element("h2",{class:"x12 sm-fs8 md+fs12 m2b"}," Productivity is in the Details "),L.element("p",{class:"x12 fs4"}," Nullstack features have been extracted from real life projects with convenience and consistency in mind "))}renderFeatures(){const e=this.renderFeature;return L.element("section",{class:"x lg-x12z xl lg-p2x"},L.element(e,{title:"Stateful Components",key:"Stateful",link:"/stateful-components"}),L.element(e,{title:"Two-Way Binding",key:"Binding",link:"/two-way-bindings"}),L.element(e,{title:"Built-in Routes",key:"Routes",link:"/routes-and-params"}),L.element(e,{title:"Full-stack Lifecycle",key:"Lifecycle",link:"/full-stack-lifecycle"}))}renderReason({title:e,description:t,closer:n,link:r}){const i=this.renderIcon;return L.element("div",{class:"md+x6 p1"},L.element("div",{class:"xx bgm2 p8y p4x y12"},L.element(i,{height:40,class:"cm2z m4b"}),L.element("h3",{class:"x12 fs6"},L.element("a",{href:r,class:"ci1"},e)),L.element("p",{class:"x12 fs4 m4y"}," ",t," "),L.element("strong",null,n)))}renderWhy(){const e=this.renderReason;return L.element("section",{class:"sm-p2x sm-m10t md+m20t"},L.element("div",{class:"x xx md+bcm2t p10y"},L.element("h2",{class:"x12 sm-fs8 md+fs12"}," Why should you use Nullstack? "),L.element("div",{class:"xl p10y"},L.element(e,{title:"Scalable Development",description:"Every project starts small and becomes complex over time. Scale as you go, no matter the size of the team.",link:"/about",closer:"No compromises, no enforcements."}),L.element(e,{title:"Feature-driven Development",description:"Development of both back and front ends of a feature in the same component in an organized way with ease of overview.",link:"/about#feature-driven",closer:"True componentization and code reusability."}),L.element(e,{title:"Already existing ecosystem",description:"Takes advantage of any isomorphic vanilla Javascript package made throughout history.",link:"/about#everything-as-vanilla-as-possible",closer:"All of your application speaks the same language."}),L.element(e,{title:"Quickly adapt to scope changes",description:"The horizontal structure, as opposed to a hierarchical one, makes it a lot easier to move resources around.",link:"/about#why-dependency-injection-instead-of-modularity",closer:"Flexibility over bureaucracy."})),L.element("a",{href:"/getting-started",class:"bci1 cm1 ci1:h bgi1 bgm1:h p2y p4x"}," Get Started "),L.element("span",{class:"x12 fs4"}," ╰(*°▽°*)╯ ")))}renderVideo({code:e,part:t}){const n="Full-stack with Nullstack - Part "+t;return L.element("div",{class:"x12 md+x4 p1"},L.element("a",{href:`https://www.youtube.com/watch?v=${e}&list=PL5ylYELQy1hyFbguVaShp3XujjdVXLpId`,title:n,target:"_blank",rel:"noopener"},L.element("img",{src:`/thumb-0${t}.webp`,alt:n,height:"209",width:"372",loading:"lazy"})))}renderPlaylist({worker:e}){const t=this.renderVideo;return!!e.online&&L.element("section",{class:"x xx md+bcm2t sm-p10t md+p20t sm-p2x"},L.element("h2",{class:"x12 sm-fs8 md+fs12"}," Watch our Nullstack video turorials "),L.element("p",{class:"x12 fs4"}," Nullstack cares about making its content as direct to the point and easy to understand as possible "),L.element("div",{class:"xl x12 p10t"},L.element(t,{code:"l23z00GEar8",part:1}),L.element(t,{code:"_i5kKXkhBaM",part:2}),L.element(t,{code:"8PExa5-G1As",part:3})))}render(){const e=this.renderWhy,t=this.renderPlaylist,n=this.renderFeatures,r=this.renderProductivity,i=this.renderShowcase,s=this.renderAbout,a=this.renderCycle,l=this.renderHero;return L.element("div",null,L.element(l,null),L.element(a,null),L.element(s,null),L.element(i,null),L.element(r,null),L.element(n,null),L.element(t,null),L.element(e,null))}};var H=class Waifu_Waifu extends L{prepare({page:e,project:t}){e.title="Nulla-Chan - "+t.name,e.description="Nullstack's official waifu"}renderAttribute({label:e,value:t}){return L.element("li",{class:"xl m2b"},L.element("b",null,e),": ",t)}render({worker:e}){const t=this.renderAttribute;return L.element("div",{class:"x md+p20t p10y sm-p2x"},L.element("div",{class:"xx x12"},e.online&&L.element("img",{src:"/waifu.png",alt:"Nulla-Chan",height:"500"}),L.element("div",{class:"md+p10l"},L.element("h1",{class:"xl m14t"}," Nulla ",L.element("span",{class:"ci1"},"-")," Chan "),L.element("p",{class:"xl m8b"}," Nullstack's official waifu "),L.element("ul",null,L.element(t,{label:"🕐 Age",value:"19"}),L.element(t,{label:"♒ Sign",value:"Aquarius"}),L.element(t,{label:"🎂 Birthday",value:"January 28"}),L.element(t,{label:"💖 Blood Type",value:"A"}),L.element(t,{label:"📏 Height",value:"1.55m"}),L.element(t,{label:"🍨 Fav Food",value:"Anything vanilla flavored"}),L.element(t,{label:"🧩 Hobby",value:"Reinventing Wheels"}),L.element(t,{label:"🧠 Neurodivergences",value:"ASD and ADHD"})),L.element("span",{class:"xl m8t"},"🎨 Created by:",L.element("a",{href:"https://www.instagram.com/biancazanette",target:"_blank",rel:"noopener",class:"ci1 m2x"}," Bilkaya ")),L.element("span",{class:"xl m2t"},"Check out the",L.element("a",{href:"https://www.artstation.com/artwork/Vg4o6R",target:"_blank",rel:"noopener",class:"ci1 m2x"}," Character Concept ")))),L.element("blockquote",{class:"xx x12 m10y"},L.element("p",{class:"x12"}," A sweet and shy perfect waifu, is always hyperfocused in her studies but somehow distracted at the same time. "),L.element("p",{class:"x12"}," She is always cheerful... until she has to face glue code or sees a post telling her which tech she should use. ")))}};var W=class Contributors_Contributors extends L{prepare({page:e,project:t}){e.title="Contributors - "+t.name,e.description="Found a bug or want a new feature? Become a contributor!"}renderParagraph({text:e}){return L.element("p",{class:"x12 fs4 m1b"},e)}renderTopic({title:e,main:t,children:n}){return L.element("div",{class:"x12 m10b"},L.element("element",{tag:t?"h1":"h2",class:"x12 sm-fs6 md+fs8 m2b"},e),n)}renderState(){const e=this.renderParagraph,t=this.renderTopic;return L.element(t,{title:"The State of Nullstack",main:!0},L.element(e,{text:"Nullstack is being developed since January 2019 with features being extracted from freelancing projects."}),L.element(e,{text:"At this point the API is stable, lifecycle methods and context keys will pretty much be frozen."}),L.element(e,{text:"We are not yet on 1.0 but really close, the only thing missing is to test it on applications made outside the core team to figure out if it fits the needs of other programmers."}))}renderTask({description:e}){return L.element("li",{class:"bc1b p1y"}," ⚔ ",e," ")}renderRoadmap(){const e=this.renderTask,t=this.renderParagraph,n=this.renderTopic;return L.element(n,{title:"The Roadmap"},L.element(t,{text:"The next updates will be guided towards fixing any bugs that are found and focus on quality of life."}),L.element(t,{text:"The following updates are the next planned steps in no particular order:"}),L.element("ul",{class:"m2t"},L.element(e,{description:"Video and text tutorials in both English and Portuguese"}),L.element(e,{description:"Internationalize the documentation"}),L.element(e,{description:"Improve error messages and unify the server and client consoles"}),L.element(e,{description:"Typescript support and better IDE support in general"}),L.element(e,{description:"Yak shaving to improve performance on things that give diminishing returns at this point"}),L.element(e,{description:"Plugins for deployment in the most common hosts"}),L.element(e,{description:"Browser dev tools for context and instances inspection"}),L.element(e,{description:"Out of the box integration with hybrid apps, if PWAs don't kill them before we get to it"})))}renderContributor({github:e,name:t,role:n,description:r,contribution:i}){return L.element("div",{class:"xl x12 bcm2 p2 m2t"},L.element("img",{class:"xl",src:"https://github.com/"+e+".png",alt:t,width:"90",height:"90",style:"height: 90px"}),L.element("div",{class:"md+x10 md+p3l sm-m3t"},L.element("h3",null," ",L.element("a",{href:"https://github.com/"+e,class:"ci1",target:"_blank",rel:"noopener"}," ",t," ")," "),L.element("h4",{class:"m1y"}," ",n," "),L.element("p",null," ",r," "),L.element("p",null," ",i," ")))}renderCoreTeam(){const e=this.renderContributor,t=this.renderParagraph,n=this.renderTopic;return L.element(n,{title:"The Core Team"},L.element(t,{text:"Nullstack was developed by full-stack neuro-atypical freelancers."}),L.element(t,{text:"With a heavy background in Rails, Ember.js, and React.js, the inspirations took from those projects might be obvious."}),L.element(e,{name:"Christian Mortaro",role:"Autistic Author",github:"Mortaro",description:"Creator of the concept. Comes with new API proposals to its favorite rubber ducks and returns with commits.",contribution:"Reverse engineered wishful thinking code into existence and then refactored it into a framework."}),L.element(e,{name:"Dayson Marx",role:"Distracted Designer",github:"daysonmarx",description:"Rubber duck with human skills that makes sure the code is not going too far outside the box, then makes the box look nice.",contribution:"API reviewer that developed third party projects to test proof of concepts from a front-end focused perspective."}),L.element(e,{name:"Anny Figueira",role:"Autistic Adopter",github:"AnnyFigueira",description:"Rubber duck with a neck to find inconsistencies and problems, waiting till an API is approved to force us into rewriting everything.",contribution:"An early adopter of the framework that developed real production applications to validate how the parts fit together."}))}renderHowToContribute(){const e=this.renderParagraph,t=this.renderTopic;return L.element(t,{title:"How to Contribute"},L.element(e,{text:"It's simple. Found a bug or want a new feature?"}),L.element("p",{class:"x12 fs4 m1b"},L.element("a",{href:"https://github.com/nullstack/nullstack/issues",target:"_blank",rel:"noopener",class:"ci1"}," Create an issue "),"or",L.element("a",{href:"https://github.com/nullstack/nullstack/issues",target:"_blank",rel:"noopener",class:"ci1"}," submit a pull request ")," with tests."))}render(){const e=this.renderHowToContribute,t=this.renderCoreTeam,n=this.renderRoadmap,r=this.renderState;return L.element("section",{class:"x sm-p4x sm-p10y md+p20y"},L.element(r,null),L.element(n,null),L.element(t,null),L.element(e,null))}};var q=class Loader_Loader extends L{static render({worker:e}){return!!e.fetching&&L.element("div",{class:"z24 pftl xx yy x12 xvw y12 yvh bgm1 op18"},L.element(cog_stroke,{animation:"spin",speed:"slow",height:40,class:"cm3"}))}};var R=class GoogleAnalytics_GoogleAnalytics extends L{hydrate({router:e,page:t,id:n}){function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config",n,{page_title:t.title,page_path:e.url}),window.addEventListener(t.event,()=>{gtag("event","page_view",{page_title:t.title,page_path:e.url})})}render({id:e,self:t}){const n="https://www.googletagmanager.com";return t.hydrated?L.element("script",{async:!0,src:`${n}/gtag/js?id=${e}`}):L.element("head",null,L.element("link",{rel:"preconnect",href:n}))}};class Application_Application extends L{prepare({page:e}){e.locale="en"}renderPreloader(){return L.element("head",null,L.element("link",{rel:"preload",href:"/roboto-v20-latin-300.woff2",as:"font",type:"font/woff2",crossorigin:!0}),L.element("link",{rel:"preload",href:"/roboto-v20-latin-500.woff2",as:"font",type:"font/woff2",crossorigin:!0}),L.element("link",{rel:"preload",href:"/crete-round-v9-latin-regular.woff2",as:"font",type:"font/woff2",crossorigin:!0}))}render(){const e=this.renderPreloader;return L.element("main",null,L.element(e,null),L.element(O,null),L.element(I,{route:"/"}),L.element(z,{route:"/documentation"}),L.element(D,{route:"/components"}),L.element(W,{route:"/contributors"}),L.element(H,{route:"/waifu"}),L.element(F,{route:"/:slug"}),L.element(E,null),L.element(R,{id:"G-E7GZ5Z4MLN"}),L.element(q,null))}}!function Application_defineProperty(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}(Application_Application,"hash","09efe9282641f2ee84d0fc952160d2a7");var Q=Application_Application;L.start(Q)}]); \ No newline at end of file diff --git a/docs/client-f0cee0769c64977a287dddeaae84c7f6.js.LICENSE.txt b/docs/client-f0cee0769c64977a287dddeaae84c7f6.js.LICENSE.txt deleted file mode 100644 index 0285de91..00000000 --- a/docs/client-f0cee0769c64977a287dddeaae84c7f6.js.LICENSE.txt +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Prism: Lightweight, robust, elegant syntax highlighting - * - * @license MIT - * @author Lea Verou - * @namespace - * @public - */ diff --git a/docs/components/index.html b/docs/components/index.html deleted file mode 100644 index ec82c050..00000000 --- a/docs/components/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - Community Components - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Community Components

    A curated list of Nullstack components made by the community.

    If you want to add a component to this list open an issue on github .

    Brazilian Inputs

    - - - \ No newline at end of file diff --git a/docs/components/index.json b/docs/components/index.json deleted file mode 100644 index b575fb6b..00000000 --- a/docs/components/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.4":{},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Community Components - Nullstack","description":"A curated list of Nullstack components made by the community","priority":0.3}} \ No newline at end of file diff --git a/docs/context-data/index.html b/docs/context-data/index.html deleted file mode 100644 index d2f5c57b..00000000 --- a/docs/context-data/index.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - Context Data - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Context Data

    The data is an object in the framework store part of your context and gives you information about the element dataset.

    -

    You can use this key to avoid polluting your DOM with invalid attributes.

    -
    -

    💡 This helps Nullstack set attributes without wasting time validating them.

    -
    -

    This key is readonly and available only in the client context.

    -

    Any data-* attributes will receive a respective camelized key on the data object.

    -

    You can assign data attributes both via data-* and a data key that accepts an object with camelized keys.

    -

    The kebab version is also available in the context.

    -
    import Nullstack from 'nullstack';
    -
    -class ContextData extends Nullstack {
    -
    -  count = 1;
    -
    -  calculate({data}) {
    -    this.count = this.count * data.multiply + data.sum;
    -  }
    -
    -  renderInner(context) {
    -    const {data} = context;
    -    return (
    -      <div data={data}>
    -        {data.frameworkName}
    -        is same as
    -        {context['data-framework-name']}
    -      </div>
    -    )
    -  }
    -  
    -  render({data}) {
    -    return (
    -      <div> 
    -        <button onclick={this.calculate} data-multiply={3} data={{sum: 2}}>
    -          Calculate
    -        </button>
    -        <Inner data-framework-name="Nullstack" />
    -      </div>
    -    )
    -  }
    -
    -}
    -
    -export default ContextData;
    -
    -
    -

    💡 Camelized keys from the data object will result in kebab attributes in the DOM.

    -
    -

    Next step

    ⚔ Learn about the context environment.

    -
    - - - \ No newline at end of file diff --git a/docs/context-data/index.json b/docs/context-data/index.json deleted file mode 100644 index 13b807e1..00000000 --- a/docs/context-data/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Context Data","html":"

    The data is an object in the framework store part of your context and gives you information about the element dataset.

    \n

    You can use this key to avoid polluting your DOM with invalid attributes.

    \n
    \n

    💡 This helps Nullstack set attributes without wasting time validating them.

    \n
    \n

    This key is readonly and available only in the client context.

    \n

    Any data-* attributes will receive a respective camelized key on the data object.

    \n

    You can assign data attributes both via data-* and a data key that accepts an object with camelized keys.

    \n

    The kebab version is also available in the context.

    \n
    import Nullstack from 'nullstack';\n\nclass ContextData extends Nullstack {\n\n  count = 1;\n\n  calculate({data}) {\n    this.count = this.count * data.multiply + data.sum;\n  }\n\n  renderInner(context) {\n    const {data} = context;\n    return (\n      <div data={data}>\n        {data.frameworkName}\n        is same as\n        {context['data-framework-name']}\n      </div>\n    )\n  }\n  \n  render({data}) {\n    return (\n      <div> \n        <button onclick={this.calculate} data-multiply={3} data={{sum: 2}}>\n          Calculate\n        </button>\n        <Inner data-framework-name=\"Nullstack\" />\n      </div>\n    )\n  }\n\n}\n\nexport default ContextData;\n
    \n
    \n

    💡 Camelized keys from the data object will result in kebab attributes in the DOM.

    \n
    \n

    Next step

    ⚔ Learn about the context environment.

    \n","description":"The data is an object in the framework store part of your context and gives you information about the element dataset."},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Context Data - Nullstack","description":"The data is an object in the framework store part of your context and gives you information about the element dataset."}} \ No newline at end of file diff --git a/docs/context-environment/index.html b/docs/context-environment/index.html deleted file mode 100644 index 0f3a7f8d..00000000 --- a/docs/context-environment/index.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - Context Environment - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Context Environment

    The environment object is in the framework store part of your context and gives you information about the current environment.

    -

    This key is readonly and available in both the client and server contexts.

    -

    The following keys are available in the object:

    -
      -
    • client: boolean
    • -
    • server: boolean
    • -
    • development: boolean
    • -
    • production: boolean
    • -
    • static: boolean
    • -
    • key: string
    • -
    -
    import Nullstack from 'nullstack';
    -
    -class Page extends Nullstack {
    - 
    -  render({environment}) {
    -    return (
    -      <div> 
    -        {environment.client && <p> I am in the client </p>}
    -        {environment.server && <p> I am in the server </p>}
    -        {environment.development && <p> I am in development mode </p>}
    -        {environment.production && <p> I am in production mode </p>}
    -        {environment.static && <p> I am a static site </p>}
    -        <p> My key is {environment.key} </p>
    -      </div>
    -    )
    -  }
    -
    -}
    -
    -export default Page;
    -
    -

    The environment key is an md5 hash of the environment folder outputs that is appended to assets and static API path in order to assist cache control.

    -

    Next step

    ⚔ Learn about the context page.

    -
    - - - \ No newline at end of file diff --git a/docs/context-environment/index.json b/docs/context-environment/index.json deleted file mode 100644 index b7f7b17b..00000000 --- a/docs/context-environment/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Context Environment","html":"

    The environment object is in the framework store part of your context and gives you information about the current environment.

    \n

    This key is readonly and available in both the client and server contexts.

    \n

    The following keys are available in the object:

    \n
      \n
    • client: boolean
    • \n
    • server: boolean
    • \n
    • development: boolean
    • \n
    • production: boolean
    • \n
    • static: boolean
    • \n
    • key: string
    • \n
    \n
    import Nullstack from 'nullstack';\n\nclass Page extends Nullstack {\n \n  render({environment}) {\n    return (\n      <div> \n        {environment.client && <p> I am in the client </p>}\n        {environment.server && <p> I am in the server </p>}\n        {environment.development && <p> I am in development mode </p>}\n        {environment.production && <p> I am in production mode </p>}\n        {environment.static && <p> I am a static site </p>}\n        <p> My key is {environment.key} </p>\n      </div>\n    )\n  }\n\n}\n\nexport default Page;\n
    \n

    The environment key is an md5 hash of the environment folder outputs that is appended to assets and static API path in order to assist cache control.

    \n

    Next step

    ⚔ Learn about the context page.

    \n","description":"The environment object is in the framework store part of your context and gives you information about the current environment"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Context Environment - Nullstack","description":"The environment object is in the framework store part of your context and gives you information about the current environment"}} \ No newline at end of file diff --git a/docs/context-page/index.html b/docs/context-page/index.html deleted file mode 100644 index 64e7204f..00000000 --- a/docs/context-page/index.html +++ /dev/null @@ -1,159 +0,0 @@ - - - - - - - Context Page - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Context Page

    The page object is a proxy in the framework store part of your context and gives you information about the document head metatags.

    -

    This key is readwrite and available only in the client context.

    -

    Page keys will be used to generate metatags during server-side rendering and must be assigned before initiate is resolved.

    -

    The following keys are available in the object:

    -
      -
    • title: string
    • -
    • image: string (absolute or relative URL)
    • -
    • description: string
    • -
    • canonical: string (absolute or relative URL)
    • -
    • locale: string
    • -
    • robots: string
    • -
    • schema: object
    • -
    • changes: string
    • -
    • priority: number
    • -
    • status: number
    • -
    -

    When the title key is assigned on the client-side, the document title will be updated.

    -

    Nullstack uses the changes and priority keys to generate the sitemap.xml.

    -

    The sitemap is generated automatically only when using static site generation and must be manually generated in server-side rendered applications

    -

    The changes key represents the changefreq key in the sitemap.xml and if assigned must be one of the following values:

    -
      -
    • always
    • -
    • hourly
    • -
    • daily
    • -
    • weekly
    • -
    • monthly
    • -
    • yearly
    • -
    • never
    • -
    -

    The priority key is a number between 0.0 and 1.0 that represents the priority key in the sitemap.xml.

    -

    Nullstack does not set a default priority, however, sitemaps assume a 0.5 priority when not explicitly set.

    -

    Besides title and locale all other keys have sensible defaults generated based on the application scope.

    -
    import Nullstack from 'nullstack';
    -
    -class Page extends Nullstack {
    -
    -  prepare({project, page}) {
    -    page.title = `${project.name} - Page Title`;
    -    page.image = '/image.jpg';
    -    page.description = 'Page meta description';
    -    page.canonical = 'http://absolute.url/canonical-link';
    -    page.locale = 'pt-BR';
    -    page.robots = 'index, follow';
    -    page.schema = {};
    -    page.changes = 'weekly';
    -    page.priority = 1;
    -  }
    -
    -  render({page}) {
    -    return (
    -      <div>
    -        <h1> {page.title} </h1>
    -        <p> {page.description} </p>
    -      </div>
    -    )
    -  }
    -
    -}
    -
    -export default Page;
    -
    -

    Custom Events

    Updating page.title will raise a custom event.

    -
    import Nullstack from 'nullstack';
    -
    -class Analytics extends Nullstack {
    -
    -  hydrate({page}) {
    -    window.addEventListener(page.event, () => {
    -      console.log(page.title);
    -    });
    -  }
    -
    -}
    -
    -export default Analytics;
    -
    -

    Error pages

    If during the server-side render process the page.status has any value besides 200, your application will receive another render pass that gives you the chance to adjust the interface according to the status.

    -

    The status key will be raised with the HTTP response.

    -

    The page status will be modified to 500 and receive another render pass if the page raise an exception while rendering.

    -

    The status of server functions responses will be set to the page.status.

    -
    import Nullstack from 'nullstack';
    -import ErrorPage from './ErrorPage';
    -import HomePage from './HomePage';
    -
    -class Application extends Nullstack {
    -
    -  // ...
    -
    -  render({page}) {
    -    return (
    -      <main>
    -        {page.status !== 200 && <ErrorPage route="*" />}
    -        <HomePage route="/" />
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -export default Application;
    -
    -
    -

    🔥 Assigning to the status key during the single-page application mode will have no effect.

    -
    -

    Next step

    ⚔ Learn about the context project.

    -
    - - - \ No newline at end of file diff --git a/docs/context-page/index.json b/docs/context-page/index.json deleted file mode 100644 index d6b1ccb4..00000000 --- a/docs/context-page/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Context Page","html":"

    The page object is a proxy in the framework store part of your context and gives you information about the document head metatags.

    \n

    This key is readwrite and available only in the client context.

    \n

    Page keys will be used to generate metatags during server-side rendering and must be assigned before initiate is resolved.

    \n

    The following keys are available in the object:

    \n
      \n
    • title: string
    • \n
    • image: string (absolute or relative URL)
    • \n
    • description: string
    • \n
    • canonical: string (absolute or relative URL)
    • \n
    • locale: string
    • \n
    • robots: string
    • \n
    • schema: object
    • \n
    • changes: string
    • \n
    • priority: number
    • \n
    • status: number
    • \n
    \n

    When the title key is assigned on the client-side, the document title will be updated.

    \n

    Nullstack uses the changes and priority keys to generate the sitemap.xml.

    \n

    The sitemap is generated automatically only when using static site generation and must be manually generated in server-side rendered applications

    \n

    The changes key represents the changefreq key in the sitemap.xml and if assigned must be one of the following values:

    \n
      \n
    • always
    • \n
    • hourly
    • \n
    • daily
    • \n
    • weekly
    • \n
    • monthly
    • \n
    • yearly
    • \n
    • never
    • \n
    \n

    The priority key is a number between 0.0 and 1.0 that represents the priority key in the sitemap.xml.

    \n

    Nullstack does not set a default priority, however, sitemaps assume a 0.5 priority when not explicitly set.

    \n

    Besides title and locale all other keys have sensible defaults generated based on the application scope.

    \n
    import Nullstack from 'nullstack';\n\nclass Page extends Nullstack {\n\n  prepare({project, page}) {\n    page.title = `${project.name} - Page Title`;\n    page.image = '/image.jpg';\n    page.description = 'Page meta description';\n    page.canonical = 'http://absolute.url/canonical-link';\n    page.locale = 'pt-BR';\n    page.robots = 'index, follow';\n    page.schema = {};\n    page.changes = 'weekly';\n    page.priority = 1;\n  }\n\n  render({page}) {\n    return (\n      <div>\n        <h1> {page.title} </h1>\n        <p> {page.description} </p>\n      </div>\n    )\n  }\n\n}\n\nexport default Page;\n
    \n

    Custom Events

    Updating page.title will raise a custom event.

    \n
    import Nullstack from 'nullstack';\n\nclass Analytics extends Nullstack {\n\n  hydrate({page}) {\n    window.addEventListener(page.event, () => {\n      console.log(page.title);\n    });\n  }\n\n}\n\nexport default Analytics;\n
    \n

    Error pages

    If during the server-side render process the page.status has any value besides 200, your application will receive another render pass that gives you the chance to adjust the interface according to the status.

    \n

    The status key will be raised with the HTTP response.

    \n

    The page status will be modified to 500 and receive another render pass if the page raise an exception while rendering.

    \n

    The status of server functions responses will be set to the page.status.

    \n
    import Nullstack from 'nullstack';\nimport ErrorPage from './ErrorPage';\nimport HomePage from './HomePage';\n\nclass Application extends Nullstack {\n\n  // ...\n\n  render({page}) {\n    return (\n      <main>\n        {page.status !== 200 && <ErrorPage route=\"*\" />}\n        <HomePage route=\"/\" />\n      </main>\n    )\n  }\n\n}\n\nexport default Application;\n
    \n
    \n

    🔥 Assigning to the status key during the single-page application mode will have no effect.

    \n
    \n

    Next step

    ⚔ Learn about the context project.

    \n","description":"The page object is a proxy in the framework store part of your context and gives you information about the document head metatags"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Context Page - Nullstack","description":"The page object is a proxy in the framework store part of your context and gives you information about the document head metatags"}} \ No newline at end of file diff --git a/docs/context-project/index.html b/docs/context-project/index.html deleted file mode 100644 index 01af05cf..00000000 --- a/docs/context-project/index.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - Context Project - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Context Project

    The project object is a proxy in the framework store part of your context and gives you information about the app manifest and some metatags.

    -

    This key is readwrite in the server context.

    -

    This key is readonly in the client context.

    -

    Project keys will be used to generate metatags during server-side rendering and must be assigned before initiate is resolved.

    -

    Project keys will be used to generate the app manifest and should be set during the application startup.

    -

    The disallow key will be used to generate the robots.txt and should be set during the application startup.

    -

    Project keys are frozen after the application startup.

    -

    The following keys are available in the object:

    -
      -
    • domain: string
    • -
    • name: string
    • -
    • shortName: string
    • -
    • color: string
    • -
    • backgroundColor: string
    • -
    • type: string
    • -
    • display: string
    • -
    • orientation: string
    • -
    • scope: string
    • -
    • root: string
    • -
    • icons: object
    • -
    • favicon: string (relative or absolute url)
    • -
    • disallow: string array (relative paths)
    • -
    • sitemap: boolean or string (relative or absolute url)
    • -
    • cdn: string (absolute url)
    • -
    • protocol string (http or https)
    • -
    -

    Besides domain, name and color all other keys have sensible defaults generated based on the application scope.

    -

    If you do not declare the icons key, Nullstack will scan any icons with the name following the pattern "icon-[WIDTH]x[HEIGHT].png" in your public folder.

    -

    If the sitemap key is set to true your robots.txt file will point the sitemap to https://${project.domain}/sitemap.xml.

    -

    The cdn key will prefix your asset bundles and will be available in the context so you can manually prefix other assets.

    -

    The protocol key is "http" in development mode and "https" in production mode by default

    -
    import Nullstack from 'nullstack';
    -
    -class Application extends Nullstack {
    -
    -  static async start({project}) {
    -    project.name = 'Nullstack';
    -    project.shortName = 'Nullstack';
    -    project.domain = 'nullstack.app';
    -    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',
    -      '128': '/icon-128x128.png',
    -      '512': '/icon-512x512.png'
    -    };
    -    project.favicon = '/favicon.png';
    -    project.disallow = ['/admin'];
    -    project.sitemap = true;
    -    project.cdn = 'cdn.nullstack.app';
    -    project.protocol = 'https';
    -  }
    -
    -  prepare({project, page}) {
    -    page.title = project.name;
    -  }
    -
    -}
    -
    -export default Application;
    -
    -
    -

    💡 You can override the automatically generated manifest.json and robots.txt by serving your own file from the public folder

    -
    -

    Next step

    ⚔ Learn about the context settings.

    -
    - - - \ No newline at end of file diff --git a/docs/context-project/index.json b/docs/context-project/index.json deleted file mode 100644 index a48fa588..00000000 --- a/docs/context-project/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Context Project","html":"

    The project object is a proxy in the framework store part of your context and gives you information about the app manifest and some metatags.

    \n

    This key is readwrite in the server context.

    \n

    This key is readonly in the client context.

    \n

    Project keys will be used to generate metatags during server-side rendering and must be assigned before initiate is resolved.

    \n

    Project keys will be used to generate the app manifest and should be set during the application startup.

    \n

    The disallow key will be used to generate the robots.txt and should be set during the application startup.

    \n

    Project keys are frozen after the application startup.

    \n

    The following keys are available in the object:

    \n
      \n
    • domain: string
    • \n
    • name: string
    • \n
    • shortName: string
    • \n
    • color: string
    • \n
    • backgroundColor: string
    • \n
    • type: string
    • \n
    • display: string
    • \n
    • orientation: string
    • \n
    • scope: string
    • \n
    • root: string
    • \n
    • icons: object
    • \n
    • favicon: string (relative or absolute url)
    • \n
    • disallow: string array (relative paths)
    • \n
    • sitemap: boolean or string (relative or absolute url)
    • \n
    • cdn: string (absolute url)
    • \n
    • protocol string (http or https)
    • \n
    \n

    Besides domain, name and color all other keys have sensible defaults generated based on the application scope.

    \n

    If you do not declare the icons key, Nullstack will scan any icons with the name following the pattern "icon-[WIDTH]x[HEIGHT].png" in your public folder.

    \n

    If the sitemap key is set to true your robots.txt file will point the sitemap to https://${project.domain}/sitemap.xml.

    \n

    The cdn key will prefix your asset bundles and will be available in the context so you can manually prefix other assets.

    \n

    The protocol key is "http" in development mode and "https" in production mode by default

    \n
    import Nullstack from 'nullstack';\n\nclass Application extends Nullstack {\n\n  static async start({project}) {\n    project.name = 'Nullstack';\n    project.shortName = 'Nullstack';\n    project.domain = 'nullstack.app';\n    project.color = '#d22365';\n    project.backgroundColor = '#d22365';\n    project.type = 'website';\n    project.display = 'standalone';\n    project.orientation = 'portrait';\n    project.scope = '/';\n    project.root = '/';\n    project.icons = {\n      '72': '/icon-72x72.png',\n      '128': '/icon-128x128.png',\n      '512': '/icon-512x512.png'\n    };\n    project.favicon = '/favicon.png';\n    project.disallow = ['/admin'];\n    project.sitemap = true;\n    project.cdn = 'cdn.nullstack.app';\n    project.protocol = 'https';\n  }\n\n  prepare({project, page}) {\n    page.title = project.name;\n  }\n\n}\n\nexport default Application;\n
    \n
    \n

    💡 You can override the automatically generated manifest.json and robots.txt by serving your own file from the public folder

    \n
    \n

    Next step

    ⚔ Learn about the context settings.

    \n","description":"The project object is a proxy in the framework store part of your context and gives you information about the app manifest and some metatags"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Context Project - Nullstack","description":"The project object is a proxy in the framework store part of your context and gives you information about the app manifest and some metatags"}} \ No newline at end of file diff --git a/docs/context-secrets/index.html b/docs/context-secrets/index.html deleted file mode 100644 index 9bc57550..00000000 --- a/docs/context-secrets/index.html +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - Context Secrets - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Context Secrets

    The secrets object is a proxy in the framework store part of your context which you can use to configure your application with private information.

    -

    This key is readwrite and available only in the server context.

    -

    Secrets keys are frozen after the application startup.

    -

    The following keys are available in the object:

    -
      -
    • development: object
    • -
    • production: object
    • -
    • [anySetting]: any
    • -
    -

    You can assign keys to development or production keys in order to have different secrets per environment.

    -

    If you assign a key directly to the secrets object it will be available in both environments.

    -

    When reading from a key you must read directly from the secrets object and Nullstack will return the best-suited value for that environment.

    -
    import Nullstack from 'nullstack';
    -
    -class Application extends Nullstack {
    -
    -  static async start({secrets}) {
    -    secrets.development.privateKey = 'SANDBOX_API_KEY';
    -    secrets.production.privateKey = 'PRODUCTION_API_KEY';
    -    secrets.endpoint = 'https://domain.com/api';
    -  }
    -
    -  static async fetchFromApi({secrets}) {
    -    const response = await fetch(secrets.endpoint, {
    -      headers: {
    -        Authorization: `Bearer ${secrets.privateKey}`
    -      }
    -    });
    -    return await response.json();
    -  }
    -
    -}
    -
    -export default Application;
    -
    -

    Any environment key starting with NULLSTACK_SECRETS_ will be mapped to the secrets in that environment.

    -
    -

    🐱‍💻 NULLSTACK_SECRETS_PRIVATE_KEY will be mapped to secrets.privateKey

    -
    -

    Next step

    ⚔ Learn about the instance self.

    -
    - - - \ No newline at end of file diff --git a/docs/context-secrets/index.json b/docs/context-secrets/index.json deleted file mode 100644 index d336d3a9..00000000 --- a/docs/context-secrets/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Context Secrets","html":"

    The secrets object is a proxy in the framework store part of your context which you can use to configure your application with private information.

    \n

    This key is readwrite and available only in the server context.

    \n

    Secrets keys are frozen after the application startup.

    \n

    The following keys are available in the object:

    \n
      \n
    • development: object
    • \n
    • production: object
    • \n
    • [anySetting]: any
    • \n
    \n

    You can assign keys to development or production keys in order to have different secrets per environment.

    \n

    If you assign a key directly to the secrets object it will be available in both environments.

    \n

    When reading from a key you must read directly from the secrets object and Nullstack will return the best-suited value for that environment.

    \n
    import Nullstack from 'nullstack';\n\nclass Application extends Nullstack {\n\n  static async start({secrets}) {\n    secrets.development.privateKey = 'SANDBOX_API_KEY';\n    secrets.production.privateKey = 'PRODUCTION_API_KEY';\n    secrets.endpoint = 'https://domain.com/api';\n  }\n\n  static async fetchFromApi({secrets}) {\n    const response = await fetch(secrets.endpoint, {\n      headers: {\n        Authorization: `Bearer ${secrets.privateKey}`\n      }\n    });\n    return await response.json();\n  }\n\n}\n\nexport default Application;\n
    \n

    Any environment key starting with NULLSTACK_SECRETS_ will be mapped to the secrets in that environment.

    \n
    \n

    🐱‍💻 NULLSTACK_SECRETS_PRIVATE_KEY will be mapped to secrets.privateKey

    \n
    \n

    Next step

    ⚔ Learn about the instance self.

    \n","description":"The secrets object is a proxy in the framework store part of your context which you can use to configure your application with private information"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Context Secrets - Nullstack","description":"The secrets object is a proxy in the framework store part of your context which you can use to configure your application with private information"}} \ No newline at end of file diff --git a/docs/context-settings/index.html b/docs/context-settings/index.html deleted file mode 100644 index eb0ab4b7..00000000 --- a/docs/context-settings/index.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - Context Settings - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Context Settings

    The settings object is a proxy in the framework store part of your context which you can use to configure your application with public information.

    -

    This key is readwrite in the server context.

    -

    This key is readonly in the client context.

    -

    Settings keys are frozen after the application startup.

    -

    The following keys are available in the object:

    -
      -
    • development: object
    • -
    • production: object
    • -
    • [anySetting]: any
    • -
    -

    You can assign keys to development or production keys in order to have different settings per environment.

    -

    If you assign a key directly to the settings object it will be available in both environments.

    -

    When reading from a key you must read directly from the settings object and Nullstack will return the best-suited value for that environment.

    -
    import Nullstack from 'nullstack';
    -
    -class Application extends Nullstack {
    -
    -  static async start({settings}) {
    -    settings.development.publicKey = 'SANDBOX_API_KEY';
    -    settings.production.publicKey = 'PRODUCTION_API_KEY';
    -    settings.endpoint = 'https://domain.com/api';
    -  }
    -
    -  async hydrate({settings}) {
    -    const response = await fetch(settings.endpoint, {
    -      headers: {
    -        Authorization: `Bearer ${settings.publicKey}`
    -      }
    -    });
    -    this.data = await response.json();
    -  }
    -
    -}
    -
    -export default Application;
    -
    -

    Any environment key starting with NULLSTACK_SETTINGS_ will be mapped to the settings in that environment.

    -
    -

    🐱‍💻 NULLSTACK_SETTINGS_PUBLIC_KEY will be mapped to settings.publicKey

    -
    -

    Next step

    ⚔ Learn about the context secrets.

    -
    - - - \ No newline at end of file diff --git a/docs/context-settings/index.json b/docs/context-settings/index.json deleted file mode 100644 index 7caaa5d1..00000000 --- a/docs/context-settings/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Context Settings","html":"

    The settings object is a proxy in the framework store part of your context which you can use to configure your application with public information.

    \n

    This key is readwrite in the server context.

    \n

    This key is readonly in the client context.

    \n

    Settings keys are frozen after the application startup.

    \n

    The following keys are available in the object:

    \n
      \n
    • development: object
    • \n
    • production: object
    • \n
    • [anySetting]: any
    • \n
    \n

    You can assign keys to development or production keys in order to have different settings per environment.

    \n

    If you assign a key directly to the settings object it will be available in both environments.

    \n

    When reading from a key you must read directly from the settings object and Nullstack will return the best-suited value for that environment.

    \n
    import Nullstack from 'nullstack';\n\nclass Application extends Nullstack {\n\n  static async start({settings}) {\n    settings.development.publicKey = 'SANDBOX_API_KEY';\n    settings.production.publicKey = 'PRODUCTION_API_KEY';\n    settings.endpoint = 'https://domain.com/api';\n  }\n\n  async hydrate({settings}) {\n    const response = await fetch(settings.endpoint, {\n      headers: {\n        Authorization: `Bearer ${settings.publicKey}`\n      }\n    });\n    this.data = await response.json();\n  }\n\n}\n\nexport default Application;\n
    \n

    Any environment key starting with NULLSTACK_SETTINGS_ will be mapped to the settings in that environment.

    \n
    \n

    🐱‍💻 NULLSTACK_SETTINGS_PUBLIC_KEY will be mapped to settings.publicKey

    \n
    \n

    Next step

    ⚔ Learn about the context secrets.

    \n","description":"The settings object is a proxy in the framework store part of your context which you can use to configure your application with public information"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Context Settings - Nullstack","description":"The settings object is a proxy in the framework store part of your context which you can use to configure your application with public information"}} \ No newline at end of file diff --git a/docs/context/index.html b/docs/context/index.html deleted file mode 100644 index 4ca7d7ef..00000000 --- a/docs/context/index.html +++ /dev/null @@ -1,198 +0,0 @@ - - - - - - - Context - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Context

    Every function in Nullstack receives a context as the argument.

    -

    There are two contexts, one for the client and another one for the server.

    -

    The client context lives as long as the browser tab is open.

    -

    The server context lives as long as the server is running.

    -

    Both contexts are proxies that merge the keys of 3 objects:

    -

    1 - Framework store

    These are the information that the framework makes available to you by default.

    -

    The available global server keys are:

    -

    The available global client keys are:

    -

    The available instance client keys are:

    -

    2 - Application store

    When you set a key to the context it will be available for destructuring at any depth of the application, even the parents of your component or 3rd party applications that mount your component.

    -

    Updating a key in the context causes the application to re-render automatically.

    -

    You can think of this as a single concept to replace stores, contexts, services, and reducers at the same time using the dependency injection pattern with vanilla javascript objects instead.

    -
    import Nullstack from 'nullstack';
    -
    -class Counter extends Nullstack {
    -
    -  prepare(context) {
    -    context.count = 1;
    -  }
    -
    -  static async updateTotalCount(context) {
    -    context.totalCount += count;
    -  }
    -
    -  async double(context) {
    -    context.count += context.count;
    -    await this.updateTotalCount({count: context.count});
    -  }
    -  
    -  render({count}) {
    -    return (
    -      <button onclick={this.double}> {count} </button>
    -    )
    -  }
    -
    -}
    -
    -export default Counter;
    -
    -
    import Nullstack from 'nullstack';
    -import Counter from './Counter';
    -
    -class Application extends Nullstack {
    -
    -  static async start(context) {
    -    context.totalCount = 0;
    -  }
    - 
    -  render({count}) {
    -    return (
    -      <main>
    -        {(!count || count < 10) && <Counter />}
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -export default Application;
    -
    -

    3 - Attributes

    These are the attributes you declare in your tag.

    -

    If the attribute is declared in a component tag every function of that component will have access to that attribute in its context.

    -

    If the attribute is declared in a tag that has an event it will be merged into the event function context.

    -
    import Nullstack from 'nullstack';
    -
    -class Counter extends Nullstack {
    -
    -  add(context) {
    -    context.count += context.delta + context.amount;
    -  }
    -  
    -  render({count, delta}) {
    -    return (
    -      <button onclick={this.add} amount={1}> 
    -        add {delta} to {count}
    -      </button>
    -    )
    -  }
    -
    -}
    -
    -export default Counter;
    -
    -
    import Nullstack from 'nullstack';
    -import Counter from './Counter';
    -
    -class Application extends Nullstack {
    -
    -  prepare(context) {
    -    context.count = 0;
    -  }
    - 
    -  render() {
    -    return (
    -      <main>
    -        <Counter delta={2} />}
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -export default Application;
    -
    -

    Functions Context

    Every function of subclasses of Nullstack is injected with a copy of the instance context merged with its arguments.

    -

    Arguments are optional, but if declared, must be a single object with keys of your choice.

    -
    import Nullstack from 'nullstack';
    -
    -class Counter extends Nullstack {
    -
    -  prepare() {
    -    this.add();
    -    this.add({amount: 2});
    -  }
    -
    -  add(context) {
    -    context.count += context.amount || 1;
    -  }
    -
    -}
    -
    -export default Counter;
    -
    -

    Next step

    ⚔ Learn about routes and params.

    -
    - - - \ No newline at end of file diff --git a/docs/context/index.json b/docs/context/index.json deleted file mode 100644 index ddf6e05e..00000000 --- a/docs/context/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Context","html":"

    Every function in Nullstack receives a context as the argument.

    \n

    There are two contexts, one for the client and another one for the server.

    \n

    The client context lives as long as the browser tab is open.

    \n

    The server context lives as long as the server is running.

    \n

    Both contexts are proxies that merge the keys of 3 objects:

    \n

    1 - Framework store

    These are the information that the framework makes available to you by default.

    \n

    The available global server keys are:

    \n

    The available global client keys are:

    \n

    The available instance client keys are:

    \n

    2 - Application store

    When you set a key to the context it will be available for destructuring at any depth of the application, even the parents of your component or 3rd party applications that mount your component.

    \n

    Updating a key in the context causes the application to re-render automatically.

    \n

    You can think of this as a single concept to replace stores, contexts, services, and reducers at the same time using the dependency injection pattern with vanilla javascript objects instead.

    \n
    import Nullstack from 'nullstack';\n\nclass Counter extends Nullstack {\n\n  prepare(context) {\n    context.count = 1;\n  }\n\n  static async updateTotalCount(context) {\n    context.totalCount += count;\n  }\n\n  async double(context) {\n    context.count += context.count;\n    await this.updateTotalCount({count: context.count});\n  }\n  \n  render({count}) {\n    return (\n      <button onclick={this.double}> {count} </button>\n    )\n  }\n\n}\n\nexport default Counter;\n
    \n
    import Nullstack from 'nullstack';\nimport Counter from './Counter';\n\nclass Application extends Nullstack {\n\n  static async start(context) {\n    context.totalCount = 0;\n  }\n \n  render({count}) {\n    return (\n      <main>\n        {(!count || count < 10) && <Counter />}\n      </main>\n    )\n  }\n\n}\n\nexport default Application;\n
    \n

    3 - Attributes

    These are the attributes you declare in your tag.

    \n

    If the attribute is declared in a component tag every function of that component will have access to that attribute in its context.

    \n

    If the attribute is declared in a tag that has an event it will be merged into the event function context.

    \n
    import Nullstack from 'nullstack';\n\nclass Counter extends Nullstack {\n\n  add(context) {\n    context.count += context.delta + context.amount;\n  }\n  \n  render({count, delta}) {\n    return (\n      <button onclick={this.add} amount={1}> \n        add {delta} to {count}\n      </button>\n    )\n  }\n\n}\n\nexport default Counter;\n
    \n
    import Nullstack from 'nullstack';\nimport Counter from './Counter';\n\nclass Application extends Nullstack {\n\n  prepare(context) {\n    context.count = 0;\n  }\n \n  render() {\n    return (\n      <main>\n        <Counter delta={2} />}\n      </main>\n    )\n  }\n\n}\n\nexport default Application;\n
    \n

    Functions Context

    Every function of subclasses of Nullstack is injected with a copy of the instance context merged with its arguments.

    \n

    Arguments are optional, but if declared, must be a single object with keys of your choice.

    \n
    import Nullstack from 'nullstack';\n\nclass Counter extends Nullstack {\n\n  prepare() {\n    this.add();\n    this.add({amount: 2});\n  }\n\n  add(context) {\n    context.count += context.amount || 1;\n  }\n\n}\n\nexport default Counter;\n
    \n

    Next step

    ⚔ Learn about routes and params.

    \n","description":"Create full-stack javascript applications within seconds"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Context - Nullstack","description":"Create full-stack javascript applications within seconds"}} \ No newline at end of file diff --git a/docs/contributors/index.html b/docs/contributors/index.html deleted file mode 100644 index 6e736307..00000000 --- a/docs/contributors/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - Contributors - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    The State of Nullstack

    Nullstack is being developed since January 2019 with features being extracted from freelancing projects.

    At this point the API is stable, lifecycle methods and context keys will pretty much be frozen.

    We are not yet on 1.0 but really close, the only thing missing is to test it on applications made outside the core team to figure out if it fits the needs of other programmers.

    The Roadmap

    The next updates will be guided towards fixing any bugs that are found and focus on quality of life.

    The following updates are the next planned steps in no particular order:

    • Video and text tutorials in both English and Portuguese
    • Internationalize the documentation
    • Improve error messages and unify the server and client consoles
    • Typescript support and better IDE support in general
    • Yak shaving to improve performance on things that give diminishing returns at this point
    • Plugins for deployment in the most common hosts
    • Browser dev tools for context and instances inspection
    • Out of the box integration with hybrid apps, if PWAs don't kill them before we get to it

    The Core Team

    Nullstack was developed by full-stack neuro-atypical freelancers.

    With a heavy background in Rails, Ember.js, and React.js, the inspirations took from those projects might be obvious.

    Christian Mortaro

    Christian Mortaro

    Autistic Author

    Creator of the concept. Comes with new API proposals to its favorite rubber ducks and returns with commits.

    Reverse engineered wishful thinking code into existence and then refactored it into a framework.

    Dayson Marx

    Dayson Marx

    Distracted Designer

    Rubber duck with human skills that makes sure the code is not going too far outside the box, then makes the box look nice.

    API reviewer that developed third party projects to test proof of concepts from a front-end focused perspective.

    Anny Figueira

    Anny Figueira

    Autistic Adopter

    Rubber duck with a neck to find inconsistencies and problems, waiting till an API is approved to force us into rewriting everything.

    An early adopter of the framework that developed real production applications to validate how the parts fit together.

    How to Contribute

    It's simple. Found a bug or want a new feature?

    Create an issue or submit a pull request with tests.

    - - - \ No newline at end of file diff --git a/docs/contributors/index.json b/docs/contributors/index.json deleted file mode 100644 index fb3a5a6d..00000000 --- a/docs/contributors/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.5":{},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Contributors - Nullstack","description":"Found a bug or want a new feature? Become a contributor!"}} \ No newline at end of file diff --git a/docs/crete-round-v9-latin-regular.woff2 b/docs/crete-round-v9-latin-regular.woff2 deleted file mode 100644 index 54ef1dc7..00000000 Binary files a/docs/crete-round-v9-latin-regular.woff2 and /dev/null differ diff --git a/docs/documentation/index.html b/docs/documentation/index.html deleted file mode 100644 index f304dcbe..00000000 --- a/docs/documentation/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - Documentation - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/documentation/index.json b/docs/documentation/index.json deleted file mode 100644 index aa413c1d..00000000 --- a/docs/documentation/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.3":{},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Documentation - Nullstack","description":"Follow these steps and become a full-stack javascript developer!","priority":0.8}} \ No newline at end of file diff --git a/docs/favicon-96x96.png b/docs/favicon-96x96.png deleted file mode 100644 index a2cec666..00000000 Binary files a/docs/favicon-96x96.png and /dev/null differ diff --git a/docs/full-stack-lifecycle/index.html b/docs/full-stack-lifecycle/index.html deleted file mode 100644 index a74f71f1..00000000 --- a/docs/full-stack-lifecycle/index.html +++ /dev/null @@ -1,174 +0,0 @@ - - - - - - - Full-Stack Lifecycle - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Full-Stack Lifecycle

    Lifecycle methods are special named functions that you can declare in the class.

    -

    Each lifecycle method runs in a specific order in a queue so it's guaranteed that all components initiated in that cycle will be prepared before the first one is initiated.

    -

    Prepare

    This method is blocking and runs before the first time the component is rendered.

    -

    You can use this function to set the state that the user will see before things are loaded.

    -

    If the user is entering from this route prepare will run in the server before Nullstack server-side renders your application.

    -

    If the user is navigating from another route this method will run in the client.

    -
    import Nullstack from 'nullstack';
    -
    -class Component extends Nullstack {
    -
    -  // ...
    -
    -  prepare() {
    -    this.date = new Date();
    -  }
    -
    -  // ...
    -
    -}
    -
    -export default Component;
    -
    -

    Initiate

    This method can be async and runs right after the component is prepared and rendered for the first time.

    -

    You can use this function to invoke another server function and load the data to present the page.

    -

    If the user is entering from this route initiate will run in the server.

    -

    Nullstack will wait till the promise is resolved and then finally generate the HTML that will be served.

    -

    If the user is navigating from another route this method will run in the client.

    -
    import Nullstack from 'nullstack';
    -
    -class Component extends Nullstack {
    -
    -  // ...
    -
    -  async initiate() {
    -    this.task = await getTaskByDay({
    -      day: this.date
    -    });
    -  }
    -
    -  // ...
    -
    -}
    -
    -export default Component;
    -
    -
    -

    ✨ Learn more about server functions.

    -
    -

    Hydrate

    This method is async and will only run in the client.

    -

    This method will always run no matter which environment started the component.

    -

    This is a good place to trigger dependencies that manipulate the dom or can only run on the client-side.

    -
    import Nullstack from 'nullstack';
    -
    -class Component extends Nullstack {
    -
    -  // ...
    -
    -  async hydrate() {
    -    this.timer = setInterval(() => {
    -      console.log(this.date);
    -    }, 1000);
    -  }
    -
    -  // ...
    -
    -}
    -
    -export default Component;
    -
    -

    Update

    This method is async and will only run in the client.

    -

    This method runs on every component anytime the application state changes.

    -
    -

    🔥 Be careful not to cause infinite loopings when mutating state inside update.

    -
    -

    This will run right before rendering but will not block the rendering queue.

    -

    The update function will not start running until the application is rendered after the initiate.

    -
    import Nullstack from 'nullstack';
    -
    -class Component extends Nullstack {
    -
    -  // ...
    -
    -  async update() {
    -    const today = new Date();
    -    if(today.getDay() != this.date.getDay()) {
    -      this.date = today;
    -      await this.initiate();
    -    }
    -  }
    -
    -  // ...
    -
    -}
    -
    -export default Component;
    -
    -
    -

    ✨ Lifecycle methods have no special side effects, you can call them manually without causing problems.

    -
    -

    Terminate

    This method is async and will only run in the client.

    -

    This method will run after your component leaves the DOM.

    -

    This is the place to clean up whatever you set up in the hydrate method.

    -

    The instance will be garbage collected after the promise is resolved.

    -
    import Nullstack from 'nullstack';
    -
    -class Component extends Nullstack {
    -
    -  // ...
    -
    -  async terminate() {
    -    clearInterval(this.timer);
    -  }
    -
    -  // ...
    -
    -}
    -
    -export default Component;
    -
    -

    Next steps

    ⚔ Learn about server functions.

    -
    - - - \ No newline at end of file diff --git a/docs/full-stack-lifecycle/index.json b/docs/full-stack-lifecycle/index.json deleted file mode 100644 index 26920fc4..00000000 --- a/docs/full-stack-lifecycle/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Full-Stack Lifecycle","html":"

    Lifecycle methods are special named functions that you can declare in the class.

    \n

    Each lifecycle method runs in a specific order in a queue so it's guaranteed that all components initiated in that cycle will be prepared before the first one is initiated.

    \n

    Prepare

    This method is blocking and runs before the first time the component is rendered.

    \n

    You can use this function to set the state that the user will see before things are loaded.

    \n

    If the user is entering from this route prepare will run in the server before Nullstack server-side renders your application.

    \n

    If the user is navigating from another route this method will run in the client.

    \n
    import Nullstack from 'nullstack';\n\nclass Component extends Nullstack {\n\n  // ...\n\n  prepare() {\n    this.date = new Date();\n  }\n\n  // ...\n\n}\n\nexport default Component;\n
    \n

    Initiate

    This method can be async and runs right after the component is prepared and rendered for the first time.

    \n

    You can use this function to invoke another server function and load the data to present the page.

    \n

    If the user is entering from this route initiate will run in the server.

    \n

    Nullstack will wait till the promise is resolved and then finally generate the HTML that will be served.

    \n

    If the user is navigating from another route this method will run in the client.

    \n
    import Nullstack from 'nullstack';\n\nclass Component extends Nullstack {\n\n  // ...\n\n  async initiate() {\n    this.task = await getTaskByDay({\n      day: this.date\n    });\n  }\n\n  // ...\n\n}\n\nexport default Component;\n
    \n
    \n

    ✨ Learn more about server functions.

    \n
    \n

    Hydrate

    This method is async and will only run in the client.

    \n

    This method will always run no matter which environment started the component.

    \n

    This is a good place to trigger dependencies that manipulate the dom or can only run on the client-side.

    \n
    import Nullstack from 'nullstack';\n\nclass Component extends Nullstack {\n\n  // ...\n\n  async hydrate() {\n    this.timer = setInterval(() => {\n      console.log(this.date);\n    }, 1000);\n  }\n\n  // ...\n\n}\n\nexport default Component;\n
    \n

    Update

    This method is async and will only run in the client.

    \n

    This method runs on every component anytime the application state changes.

    \n
    \n

    🔥 Be careful not to cause infinite loopings when mutating state inside update.

    \n
    \n

    This will run right before rendering but will not block the rendering queue.

    \n

    The update function will not start running until the application is rendered after the initiate.

    \n
    import Nullstack from 'nullstack';\n\nclass Component extends Nullstack {\n\n  // ...\n\n  async update() {\n    const today = new Date();\n    if(today.getDay() != this.date.getDay()) {\n      this.date = today;\n      await this.initiate();\n    }\n  }\n\n  // ...\n\n}\n\nexport default Component;\n
    \n
    \n

    ✨ Lifecycle methods have no special side effects, you can call them manually without causing problems.

    \n
    \n

    Terminate

    This method is async and will only run in the client.

    \n

    This method will run after your component leaves the DOM.

    \n

    This is the place to clean up whatever you set up in the hydrate method.

    \n

    The instance will be garbage collected after the promise is resolved.

    \n
    import Nullstack from 'nullstack';\n\nclass Component extends Nullstack {\n\n  // ...\n\n  async terminate() {\n    clearInterval(this.timer);\n  }\n\n  // ...\n\n}\n\nexport default Component;\n
    \n

    Next steps

    ⚔ Learn about server functions.

    \n","description":"Lifecycle methods are special named functions that you can declare in the class."},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Full-Stack Lifecycle - Nullstack","description":"Lifecycle methods are special named functions that you can declare in the class."}} \ No newline at end of file diff --git a/docs/getting-started/index.html b/docs/getting-started/index.html deleted file mode 100644 index d0161080..00000000 --- a/docs/getting-started/index.html +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - Getting Started - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Getting Started

    -

    📌 You can watch a video tutorial on our Youtube Channel.

    -
    -

    Create full-stack javascript applications within seconds using npx to generate your project files from the latest template.

    -
    -

    🔥 The minimum required node.js version for development mode is 12.12.0.

    -
    -

    Replace project-name with your project name and run the command below to start a project:

    -
    npx create-nullstack-app project-name
    -
    -

    Change directory to the generated folder:

    -
    cd project-name
    -
    -

    Install the dependencies:

    -
    npm install
    -
    -

    Start the application in development mode:

    -
    npm start
    -
    -

    Understanding the generated files

    The following folders and files will be generated:

    -

    index.js

    This is the Webpack entry point.

    -

    Usually, you don't have to touch this file, but it is a convenient place to import global dependencies like CSS frameworks.

    -

    src/

    This folder will contain the actual source code of your application.

    -

    src/Application.njs

    This is your application main file.

    -
    -

    ✨ Learn more about the njs file extension.

    -
    -

    The start function will be automatically called once when you run npm start, use it to populate your server context with things like database, settings, and secrets.

    -
    -

    ✨ Learn more about the application startup.

    -
    -

    src/Application.scss

    This is an empty file just to demonstrate that you can use SCSS with nullstack.

    -

    It is a good practice to import a style file in a component with the same name.

    -
    -

    ✨ Learn more about styles.

    -
    -

    public/

    Every file in here will be available to anyone from the domain root.

    -

    By default create-nullstack-app generates the icons required for your manifest.json and images for OG meta tags.

    -
    -

    ✨ Learn more about manifest.json.

    -
    -

    Be sure to replace these images with your project identity.

    -

    .development/

    This is the compiled result of your application in development mode.

    -
    -

    🔥 Do not touch this folder

    -
    -

    .production/

    This is the compiled result of your application in production mode.

    -
    -

    🔥 Do not touch this folder

    -
    -
    -

    ✨ Learn more about how to deploy a nullstack application.

    -
    -

    Next step

    ⚔ Create your first renderable component.

    -
    - - - \ No newline at end of file diff --git a/docs/getting-started/index.json b/docs/getting-started/index.json deleted file mode 100644 index 10fdfe33..00000000 --- a/docs/getting-started/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Getting Started","html":"
    \n

    📌 You can watch a video tutorial on our Youtube Channel.

    \n
    \n

    Create full-stack javascript applications within seconds using npx to generate your project files from the latest template.

    \n
    \n

    🔥 The minimum required node.js version for development mode is 12.12.0.

    \n
    \n

    Replace project-name with your project name and run the command below to start a project:

    \n
    npx create-nullstack-app project-name\n
    \n

    Change directory to the generated folder:

    \n
    cd project-name\n
    \n

    Install the dependencies:

    \n
    npm install\n
    \n

    Start the application in development mode:

    \n
    npm start\n
    \n

    Understanding the generated files

    The following folders and files will be generated:

    \n

    index.js

    This is the Webpack entry point.

    \n

    Usually, you don't have to touch this file, but it is a convenient place to import global dependencies like CSS frameworks.

    \n

    src/

    This folder will contain the actual source code of your application.

    \n

    src/Application.njs

    This is your application main file.

    \n
    \n

    ✨ Learn more about the njs file extension.

    \n
    \n

    The start function will be automatically called once when you run npm start, use it to populate your server context with things like database, settings, and secrets.

    \n
    \n

    ✨ Learn more about the application startup.

    \n
    \n

    src/Application.scss

    This is an empty file just to demonstrate that you can use SCSS with nullstack.

    \n

    It is a good practice to import a style file in a component with the same name.

    \n
    \n

    ✨ Learn more about styles.

    \n
    \n

    public/

    Every file in here will be available to anyone from the domain root.

    \n

    By default create-nullstack-app generates the icons required for your manifest.json and images for OG meta tags.

    \n
    \n

    ✨ Learn more about manifest.json.

    \n
    \n

    Be sure to replace these images with your project identity.

    \n

    .development/

    This is the compiled result of your application in development mode.

    \n
    \n

    🔥 Do not touch this folder

    \n
    \n

    .production/

    This is the compiled result of your application in production mode.

    \n
    \n

    🔥 Do not touch this folder

    \n
    \n
    \n

    ✨ Learn more about how to deploy a nullstack application.

    \n
    \n

    Next step

    ⚔ Create your first renderable component.

    \n","description":"Create full-stack javascript applications within seconds"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Getting Started - Nullstack","description":"Create full-stack javascript applications within seconds"}} \ No newline at end of file diff --git a/docs/hero.svg b/docs/hero.svg deleted file mode 100644 index dc7dfffe..00000000 --- a/docs/hero.svg +++ /dev/null @@ -1 +0,0 @@ -hero \ No newline at end of file diff --git a/docs/how-to-deploy-a-nullstack-application/index.html b/docs/how-to-deploy-a-nullstack-application/index.html deleted file mode 100644 index c69830f1..00000000 --- a/docs/how-to-deploy-a-nullstack-application/index.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - How to Deploy - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    How to Deploy

    With Nullstack it's easy to have your application up and running in production mode.

    -
    -

    🐱‍💻 stonks

    -
    -

    Nullstack compiles your code and all your dependencies using Webpack.

    -

    The output of the compilation is moved to the .production folder and is the only folder besides public that needs to be moved into the host machine.

    -

    If you have project.cdn set you must move the public folder to the actual cdn.

    -
    -

    💡 It is important that the .production folder is present for environment detection

    -
    -

    The host machine must have at least node v8.10.0 installed.

    -

    You don't have to "npm install" in the host machine.

    -
    -

    ✨ You can configure the environment using settings and secrets

    -
    -

    To start the server just run:

    -
    node .production/server.js
    -
    -
    -

    ✨ It is recommend the usage of a process manager like PM2

    -
    -

    How to Deploy a static generated site with Nullstack

    After you generate a static site, all you have to do is move the output folder to any host machine capable of serving HTML.

    -

    Next step

    -

    🎉 Congratulations. You are done with the advanced concepts!

    -
    -

    ⚔ Learn how to use MongoDB with Nullstack.

    -
    - - - \ No newline at end of file diff --git a/docs/how-to-deploy-a-nullstack-application/index.json b/docs/how-to-deploy-a-nullstack-application/index.json deleted file mode 100644 index 06c5eafb..00000000 --- a/docs/how-to-deploy-a-nullstack-application/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"How to Deploy","html":"

    With Nullstack it's easy to have your application up and running in production mode.

    \n
    \n

    🐱‍💻 stonks

    \n
    \n

    Nullstack compiles your code and all your dependencies using Webpack.

    \n

    The output of the compilation is moved to the .production folder and is the only folder besides public that needs to be moved into the host machine.

    \n

    If you have project.cdn set you must move the public folder to the actual cdn.

    \n
    \n

    💡 It is important that the .production folder is present for environment detection

    \n
    \n

    The host machine must have at least node v8.10.0 installed.

    \n

    You don't have to "npm install" in the host machine.

    \n
    \n

    ✨ You can configure the environment using settings and secrets

    \n
    \n

    To start the server just run:

    \n
    node .production/server.js\n
    \n
    \n

    ✨ It is recommend the usage of a process manager like PM2

    \n
    \n

    How to Deploy a static generated site with Nullstack

    After you generate a static site, all you have to do is move the output folder to any host machine capable of serving HTML.

    \n

    Next step

    \n

    🎉 Congratulations. You are done with the advanced concepts!

    \n
    \n

    ⚔ Learn how to use MongoDB with Nullstack.

    \n","description":"With Nullstack it's easy to have your application up and running in production mode"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"How to Deploy - Nullstack","description":"With Nullstack it's easy to have your application up and running in production mode"}} \ No newline at end of file diff --git a/docs/how-to-use-facebook-pixel-with-nullstack/index.html b/docs/how-to-use-facebook-pixel-with-nullstack/index.html deleted file mode 100644 index b74ae574..00000000 --- a/docs/how-to-use-facebook-pixel-with-nullstack/index.html +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - Facebook's Pixel - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Facebook's Pixel

    According to developers.facebook.com:

    -

    "The Facebook pixel is a snippet of JavaScript code that allows you to track visitor activity on your website."

    -

    You can take advantage of the context and custom events to create a component that dynamically sends Pixel events.

    -

    Facebook's Pixel can only be called after hydrate to ensure it is running in the client.

    -
    import Nullstack from 'nullstack';
    -
    -class FacebookPixel extends Nullstack {
    -
    -  async hydrate({page, id}) {
    -    ! function(f, b, e, v, n, t, s) {
    -      if (f.fbq) return;
    -      n = f.fbq = function() {
    -          n.callMethod ?
    -              n.callMethod.apply(n, arguments) : n.queue.push(arguments)
    -      };
    -      if (!f._fbq) f._fbq = n;
    -      n.push = n;
    -      n.loaded = !0;
    -      n.version = '2.0';
    -      n.queue = [];
    -      t = b.createElement(e);
    -      t.async = !0;
    -      t.src = v;
    -      s = b.getElementsByTagName(e)[0];
    -      s.parentNode.insertBefore(t, s)
    -    }(window, document, 'script',
    -      'https://connect.facebook.net/en_US/fbevents.js');
    -    fbq('init', id);
    -    fbq('track', 'PageView');
    -    window.addEventListener(page.event, () => {
    -      fbq('init', id);
    -      fbq('track', 'PageView');
    -    })
    -  }
    -}
    -
    -export default FacebookPixel;
    -
    -
    import Nullstack from 'nullstack';
    -import FacebookPixel from './FacebookPixel';
    -
    -class Application extends Nullstack {
    -
    -  // ...
    -
    -  render() {
    -    return (
    -      <main>
    -        <FacebookPixel id="REPLACE_WITH_YOUR_FACEBOOK_PIXEL_ID" />
    -      </main>
    -    )
    -  }
    -
    -
    -}
    -
    -export default Application;
    -
    -

    Using a Wrapper

    Alternatively, you can install nullstack-facebook-pixel as a dependency:

    -
    npm install nullstack-facebook-pixel
    -
    -
    import Nullstack from 'nullstack';
    -import FacebookPixel from 'nullstack-facebook-pixel';
    -
    -class Application extends Nullstack {
    -
    -  // ...
    -
    -  render() {
    -    return (
    -      <main>
    -        <FacebookPixel id="REPLACE_WITH_YOUR_FACEBOOK_PIXEL_ID" />
    -      </main>
    -    )
    -  }
    -
    -
    -}
    -
    -export default Application;
    -
    -

    Next step

    -

    🎉 Congratulations. You are done with the documentation!

    -
    -

    ⚔ If you want to see this more examples please open an issue on github.

    -
    - - - \ No newline at end of file diff --git a/docs/how-to-use-facebook-pixel-with-nullstack/index.json b/docs/how-to-use-facebook-pixel-with-nullstack/index.json deleted file mode 100644 index acc4f5cc..00000000 --- a/docs/how-to-use-facebook-pixel-with-nullstack/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Facebook's Pixel","html":"

    According to developers.facebook.com:

    \n

    "The Facebook pixel is a snippet of JavaScript code that allows you to track visitor activity on your website."

    \n

    You can take advantage of the context and custom events to create a component that dynamically sends Pixel events.

    \n

    Facebook's Pixel can only be called after hydrate to ensure it is running in the client.

    \n
    import Nullstack from 'nullstack';\n\nclass FacebookPixel extends Nullstack {\n\n  async hydrate({page, id}) {\n    ! function(f, b, e, v, n, t, s) {\n      if (f.fbq) return;\n      n = f.fbq = function() {\n          n.callMethod ?\n              n.callMethod.apply(n, arguments) : n.queue.push(arguments)\n      };\n      if (!f._fbq) f._fbq = n;\n      n.push = n;\n      n.loaded = !0;\n      n.version = '2.0';\n      n.queue = [];\n      t = b.createElement(e);\n      t.async = !0;\n      t.src = v;\n      s = b.getElementsByTagName(e)[0];\n      s.parentNode.insertBefore(t, s)\n    }(window, document, 'script',\n      'https://connect.facebook.net/en_US/fbevents.js');\n    fbq('init', id);\n    fbq('track', 'PageView');\n    window.addEventListener(page.event, () => {\n      fbq('init', id);\n      fbq('track', 'PageView');\n    })\n  }\n}\n\nexport default FacebookPixel;\n
    \n
    import Nullstack from 'nullstack';\nimport FacebookPixel from './FacebookPixel';\n\nclass Application extends Nullstack {\n\n  // ...\n\n  render() {\n    return (\n      <main>\n        <FacebookPixel id=\"REPLACE_WITH_YOUR_FACEBOOK_PIXEL_ID\" />\n      </main>\n    )\n  }\n\n\n}\n\nexport default Application;\n
    \n

    Using a Wrapper

    Alternatively, you can install nullstack-facebook-pixel as a dependency:

    \n
    npm install nullstack-facebook-pixel\n
    \n
    import Nullstack from 'nullstack';\nimport FacebookPixel from 'nullstack-facebook-pixel';\n\nclass Application extends Nullstack {\n\n  // ...\n\n  render() {\n    return (\n      <main>\n        <FacebookPixel id=\"REPLACE_WITH_YOUR_FACEBOOK_PIXEL_ID\" />\n      </main>\n    )\n  }\n\n\n}\n\nexport default Application;\n
    \n

    Next step

    \n

    🎉 Congratulations. You are done with the documentation!

    \n
    \n

    ⚔ If you want to see this more examples please open an issue on github.

    \n","description":"Take advantage of the [context](/context) and [custom events](/context-page) to create a component that dynamically sends Pixel events"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Facebook's Pixel - Nullstack","description":"Take advantage of the [context](/context) and [custom events](/context-page) to create a component that dynamically sends Pixel events"}} \ No newline at end of file diff --git a/docs/how-to-use-google-analytics-with-nullstack/index.html b/docs/how-to-use-google-analytics-with-nullstack/index.html deleted file mode 100644 index f05d34a3..00000000 --- a/docs/how-to-use-google-analytics-with-nullstack/index.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - Google Analytics - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Google Analytics

    According to analytics.google.com:

    -

    "Google Analytics lets you measure your advertising ROI as well as track your Flash, video, and social networking sites and applications."

    -

    You can take advantage of the context and custom events to create a component that dynamically sends GTAG events.

    -

    GTAG can only be called after hydrate to ensure it is running in the client.

    -
    import Nullstack from 'nullstack';
    -
    -class GoogleAnalytics extends Nullstack {
    -
    -  hydrate({router, page, id}) {
    -    window.dataLayer = window.dataLayer || [];
    -    function gtag(){
    -      dataLayer.push(arguments);
    -    }
    -    gtag('js', new Date());
    -    gtag('config', id, {
    -      page_title: page.title,
    -      page_path: router.url
    -    });
    -    window.addEventListener(page.event, () => {
    -      gtag('event', 'page_view', {
    -        page_title: page.title,
    -        page_path: router.url
    -      })
    -    })
    -  }
    -  
    -  render({id}) {
    -    return (
    -      <script 
    -        async
    -        src={`https://www.googletagmanager.com/gtag/js?id=${id}`}
    -      />
    -    )
    -  }
    -
    -}
    -
    -export default GoogleAnalytics;
    -
    -
    import Nullstack from 'nullstack';
    -import GoogleAnalytics from './GoogleAnalytics';
    -
    -class Application extends Nullstack {
    -
    -  // ...
    -
    -  render() {
    -    return (
    -      <main>
    -        <GoogleAnalytics id="REPLACE_WITH_YOUR_GOOGLE_ANALYTICS_ID" />
    -      </main>
    -    )
    -  }
    -
    -
    -}
    -
    -export default Application;
    -
    -

    Using a Wrapper

    Alternatively, you can install nullstack-google-analytics as a dependency:

    -
    npm install nullstack-google-analytics
    -
    -
    import Nullstack from 'nullstack';
    -import GoogleAnalytics from 'nullstack-google-analytics';
    -
    -class Application extends Nullstack {
    -
    -  // ...
    -
    -  render() {
    -    return (
    -      <main>
    -        <GoogleAnalytics id="REPLACE_WITH_YOUR_GOOGLE_ANALYTICS_ID" />
    -      </main>
    -    )
    -  }
    -
    -
    -}
    -
    -export default Application;
    -
    -

    Next step

    ⚔ Learn how to use Facebook Pixel with Nullstack.

    -
    - - - \ No newline at end of file diff --git a/docs/how-to-use-google-analytics-with-nullstack/index.json b/docs/how-to-use-google-analytics-with-nullstack/index.json deleted file mode 100644 index f05109a5..00000000 --- a/docs/how-to-use-google-analytics-with-nullstack/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Google Analytics","html":"

    According to analytics.google.com:

    \n

    "Google Analytics lets you measure your advertising ROI as well as track your Flash, video, and social networking sites and applications."

    \n

    You can take advantage of the context and custom events to create a component that dynamically sends GTAG events.

    \n

    GTAG can only be called after hydrate to ensure it is running in the client.

    \n
    import Nullstack from 'nullstack';\n\nclass GoogleAnalytics extends Nullstack {\n\n  hydrate({router, page, id}) {\n    window.dataLayer = window.dataLayer || [];\n    function gtag(){\n      dataLayer.push(arguments);\n    }\n    gtag('js', new Date());\n    gtag('config', id, {\n      page_title: page.title,\n      page_path: router.url\n    });\n    window.addEventListener(page.event, () => {\n      gtag('event', 'page_view', {\n        page_title: page.title,\n        page_path: router.url\n      })\n    })\n  }\n  \n  render({id}) {\n    return (\n      <script \n        async\n        src={`https://www.googletagmanager.com/gtag/js?id=${id}`}\n      />\n    )\n  }\n\n}\n\nexport default GoogleAnalytics;\n
    \n
    import Nullstack from 'nullstack';\nimport GoogleAnalytics from './GoogleAnalytics';\n\nclass Application extends Nullstack {\n\n  // ...\n\n  render() {\n    return (\n      <main>\n        <GoogleAnalytics id=\"REPLACE_WITH_YOUR_GOOGLE_ANALYTICS_ID\" />\n      </main>\n    )\n  }\n\n\n}\n\nexport default Application;\n
    \n

    Using a Wrapper

    Alternatively, you can install nullstack-google-analytics as a dependency:

    \n
    npm install nullstack-google-analytics\n
    \n
    import Nullstack from 'nullstack';\nimport GoogleAnalytics from 'nullstack-google-analytics';\n\nclass Application extends Nullstack {\n\n  // ...\n\n  render() {\n    return (\n      <main>\n        <GoogleAnalytics id=\"REPLACE_WITH_YOUR_GOOGLE_ANALYTICS_ID\" />\n      </main>\n    )\n  }\n\n\n}\n\nexport default Application;\n
    \n

    Next step

    ⚔ Learn how to use Facebook Pixel with Nullstack.

    \n","description":"Take advantage of the context and custom events to create a component that dynamically sends GTAG events"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Google Analytics - Nullstack","description":"Take advantage of the context and custom events to create a component that dynamically sends GTAG events"}} \ No newline at end of file diff --git a/docs/how-to-use-mongodb-with-nullstack/index.html b/docs/how-to-use-mongodb-with-nullstack/index.html deleted file mode 100644 index 1aae6264..00000000 --- a/docs/how-to-use-mongodb-with-nullstack/index.html +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - - How to use MongoDB - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    How to use MongoDB

    According to mongodb.com:

    -

    "MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era."

    -

    You can use any database with Nullstack, but the javascript integration and flexibility of MongoDB looks especially good with Nullstack applications.

    -

    Install the MongoDB driver from npm:

    -
    npm install mongodb
    -
    -

    Configure the database credentials using secrets.

    -

    The last step is to simply assign the database connection to the server context.

    -
    import Nullstack from 'nullstack';
    -import {MongoClient} from 'mongodb';
    -
    -class Application extends Nullstack {
    -
    -  static async start(context) {
    -    const {secrets} = context;
    -    secrets.development.databaseHost = 'mongodb://localhost:27017/dbname';
    -    secrets.databaseName = 'dbname';
    -    await this.startDatabase(context);
    -  }
    -
    -  static async startDatabase(context) {
    -    const {secrets} = context;
    -    const databaseClient = new MongoClient(secrets.databaseHost);
    -    await databaseClient.connect();
    -    context.database = await databaseClient.db(secrets.databaseName);
    -  }
    -
    -}
    -
    -export default Application;
    -
    -

    The example above will make the database key available to all your server functions.

    -
    import Nullstack from 'nullstack';
    -
    -class BooksList extends Nullstack {
    -
    -  books = [];
    -
    -  static async getBooks({database}) {
    -    return await database.collection('books').find().toArray();
    -  }
    -
    -  async initiate() {
    -    this.books = await this.getBooks();
    -  }
    -
    -  // ...
    -
    -}
    -
    -export default BooksList;
    -
    -

    Next step

    ⚔ Learn how to use Google Analytics with Nullstack.

    -
    - - - \ No newline at end of file diff --git a/docs/how-to-use-mongodb-with-nullstack/index.json b/docs/how-to-use-mongodb-with-nullstack/index.json deleted file mode 100644 index aad08cc5..00000000 --- a/docs/how-to-use-mongodb-with-nullstack/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"How to use MongoDB","html":"

    According to mongodb.com:

    \n

    "MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era."

    \n

    You can use any database with Nullstack, but the javascript integration and flexibility of MongoDB looks especially good with Nullstack applications.

    \n

    Install the MongoDB driver from npm:

    \n
    npm install mongodb\n
    \n

    Configure the database credentials using secrets.

    \n

    The last step is to simply assign the database connection to the server context.

    \n
    import Nullstack from 'nullstack';\nimport {MongoClient} from 'mongodb';\n\nclass Application extends Nullstack {\n\n  static async start(context) {\n    const {secrets} = context;\n    secrets.development.databaseHost = 'mongodb://localhost:27017/dbname';\n    secrets.databaseName = 'dbname';\n    await this.startDatabase(context);\n  }\n\n  static async startDatabase(context) {\n    const {secrets} = context;\n    const databaseClient = new MongoClient(secrets.databaseHost);\n    await databaseClient.connect();\n    context.database = await databaseClient.db(secrets.databaseName);\n  }\n\n}\n\nexport default Application;\n
    \n

    The example above will make the database key available to all your server functions.

    \n
    import Nullstack from 'nullstack';\n\nclass BooksList extends Nullstack {\n\n  books = [];\n\n  static async getBooks({database}) {\n    return await database.collection('books').find().toArray();\n  }\n\n  async initiate() {\n    this.books = await this.getBooks();\n  }\n\n  // ...\n\n}\n\nexport default BooksList;\n
    \n

    Next step

    ⚔ Learn how to use Google Analytics with Nullstack.

    \n","description":"You can use any database with Nullstack, but the javascript integration and flexibility of MongoDB looks especially good with Nullstack applications"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"How to use MongoDB - Nullstack","description":"You can use any database with Nullstack, but the javascript integration and flexibility of MongoDB looks especially good with Nullstack applications"}} \ No newline at end of file diff --git a/docs/icon-128x128.png b/docs/icon-128x128.png deleted file mode 100644 index e96fe8b5..00000000 Binary files a/docs/icon-128x128.png and /dev/null differ diff --git a/docs/icon-144x144.png b/docs/icon-144x144.png deleted file mode 100644 index a87f2a70..00000000 Binary files a/docs/icon-144x144.png and /dev/null differ diff --git a/docs/icon-152x152.png b/docs/icon-152x152.png deleted file mode 100644 index 10b23e29..00000000 Binary files a/docs/icon-152x152.png and /dev/null differ diff --git a/docs/icon-180x180.png b/docs/icon-180x180.png deleted file mode 100644 index 252b3a4f..00000000 Binary files a/docs/icon-180x180.png and /dev/null differ diff --git a/docs/icon-192x192.png b/docs/icon-192x192.png deleted file mode 100644 index 450ca4ca..00000000 Binary files a/docs/icon-192x192.png and /dev/null differ diff --git a/docs/icon-384x384.png b/docs/icon-384x384.png deleted file mode 100644 index c50c7037..00000000 Binary files a/docs/icon-384x384.png and /dev/null differ diff --git a/docs/icon-512x512.png b/docs/icon-512x512.png deleted file mode 100644 index db7461c0..00000000 Binary files a/docs/icon-512x512.png and /dev/null differ diff --git a/docs/icon-72x72.png b/docs/icon-72x72.png deleted file mode 100644 index 58aaf4cd..00000000 Binary files a/docs/icon-72x72.png and /dev/null differ diff --git a/docs/icon-96x96.png b/docs/icon-96x96.png deleted file mode 100644 index a6dd92a8..00000000 Binary files a/docs/icon-96x96.png and /dev/null differ diff --git a/docs/image-1200x630.png b/docs/image-1200x630.png deleted file mode 100644 index 03da34b6..00000000 Binary files a/docs/image-1200x630.png and /dev/null differ diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 918a4415..00000000 --- a/docs/index.html +++ /dev/null @@ -1,218 +0,0 @@ - - - - - - - Nullstack - Full-stack Javascript Components - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Full-stack Javascript Components

    for one-dev armies

    Nullstack is a full-stack framework for building progressive web applications.

    It connects a stateful UI layer to specialized microservices in the same component using vanilla javascript.

    Focus on solving your business logic instead of writing glue code.

    Server-Side Rendering

    Nullstack generates SEO ready HTML optimized for the first paint of your route in a single request using local functions with zero javascript dependencies in the client bundle.

    Single Page Application

    After hydration, requests will fetch JSON from an automatically generated API off server functions, update the application state, and rerender the page.

    Static Site Generation

    You can even use Nullstack to generate lightning-fast static websites that serve HTML and become a single page application using an automatically generated static API .

    Complete Features as Components

    Nullstack is not another part of your stack, it is your stack

    Your application can be exported from back-end to front-end as a component and mounted into another application

    import Nullstack from 'nullstack';
    -import TaskList from './TaskList';
    -import {readFileSync} from 'fs';
    -
    -class Application extends Nullstack {
    -
    -  static async getTasks({limit}) {
    -    const json = readFileSync('tasks.json', 'utf-8');
    -    return JSON.parse(json).tasks.slice(0, limit);
    -  }
    -
    -  prepare(context) {
    -    context.tasks = [];
    -  }
    -
    -  async initiate(context) {
    -    context.tasks = await this.getTasks({limit: 10});
    -  }
    -
    -  render() {
    -    return (
    -      <main>
    -        <TaskList route="/tasks" />
    -        <a href="/tasks" route="*"> List Tasks </a>
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -export default Application;
    import Nullstack from 'nullstack';
    -
    -class TaskList extends Nullstack {
    -
    -  hideComplete = false;
    -
    -  renderTask({task}) {
    -    if(this.hideComplete && task.complete) return false;
    -    return (
    -      <li> 
    -        <input bind={task.description} />
    -      </li>
    -    )
    -  }
    -  
    -  render({tasks}) {
    -    return (
    -      <div> 
    -        <ul>
    -          {tasks.map((task) => <Task task={task} />)}
    -        </ul>
    -        <button onclick={{hideComplete: !this.hideComplete}}>
    -          Toggle complete tasks
    -        </button>
    -      </div>
    -    )
    -  }
    -
    -}
    -
    -export default TaskList;

    The example above uses server functions to read tasks from a JSON file and store them in the context available to all components.

    The tasks are listed in a specific route that renders a component with multiple inner components filtered by status with inputs using two-way bindings .

    Productivity is in the Details

    Nullstack features have been extracted from real life projects with convenience and consistency in mind

    import Nullstack from 'nullstack';
    -
    -class Controlled extends Nullstack {
    -
    -  count = 0;
    -
    -  increment({delta}) {
    -    this.count += delta;
    -  }
    -  
    -  render() {
    -    return (
    -      <div>
    -        <button onclick={this.increment} delta={-1}> 
    -          {this.count}
    -        </button>
    -        <span> {this.count} </span>
    -        <button onclick={this.increment} delta={1}> 
    -          {this.count}
    -        </button>
    -      </div>
    -    )
    -  }
    -
    -}
    -
    -export default Controlled;
    import Nullstack from 'nullstack';
    -
    -class Binding extends Nullstack {
    -
    -  number = 1;
    -  boolean = true;
    -  
    -  object = {number: 1};
    -  array = ['a', 'b', 'c'];
    -  
    -  render({params}) {
    -    return (
    -      <form>
    -        <input bind={this.number} />
    -        <input bind={this.boolean} type="checkbox" />
    -        <input bind={this.object.number} />
    -        {this.array.map((value, index) => (
    -          <input bind={this.array[index]} />
    -        ))}
    -        <input bind={params.page} />
    -      </form>
    -    )
    -  }
    -
    -}
    -
    -export default Binding;
    import Nullstack from 'nullstack';
    -
    -class Routes extends Nullstack {
    -
    -  renderPost({params}) {
    -    return (
    -      <div>
    -        <div route="/post/getting-started">
    -          npx create-nullstack-app name
    -        </div>
    -        <div route="*"> {params.slug} </div>
    -      </div>
    -    )
    -  }
    -  
    -  render() {
    -    return (
    -      <div> 
    -        <Post route="/post/:slug" />
    -        <a href="/post/hello-world"> Welcome </a>
    -      </div>
    -    )
    -  }
    -
    -}
    -
    -export default Routes;
    import Nullstack from 'nullstack';
    -
    -class Lifecycle extends Nullstack {
    -  
    -  prepare({environment}) {
    -    const {server, client} = environment;
    -  }
    -
    -  async initiate({environment}) {
    -    const {server, client} = environment;
    -  }
    -
    -  async hydrate({environment}) {
    -    const {client} = environment;
    -  }
    -
    -  async update({environment}) {
    -    const {client} = environment;
    -  }
    -
    -  async terminate({environment}) {
    -    const {client} = environment;
    -  }
    -
    -}
    -
    -export default Lifecycle;

    Watch our Nullstack video turorials

    Nullstack cares about making its content as direct to the point and easy to understand as possible

    Full-stack with Nullstack - Part 1
    Full-stack with Nullstack - Part 2
    Full-stack with Nullstack - Part 3

    Why should you use Nullstack?

    [object Object]

    Scalable Development

    Every project starts small and becomes complex over time. Scale as you go, no matter the size of the team.

    No compromises, no enforcements.
    [object Object]

    Feature-driven Development

    Development of both back and front ends of a feature in the same component in an organized way with ease of overview.

    True componentization and code reusability.
    [object Object]

    Already existing ecosystem

    Takes advantage of any isomorphic vanilla Javascript package made throughout history.

    All of your application speaks the same language.
    [object Object]

    Quickly adapt to scope changes

    The horizontal structure, as opposed to a hierarchical one, makes it a lot easier to move resources around.

    Flexibility over bureaucracy.
    Get Started ╰(*°▽°*)╯
    - - - \ No newline at end of file diff --git a/docs/index.json b/docs/index.json deleted file mode 100644 index 7d2aca09..00000000 --- a/docs/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.2":{},"Application":{"html":"import Nullstack from 'nullstack';\r\nimport TaskList from './TaskList';\r\nimport {readFileSync} from 'fs';\r\n\r\nclass Application extends Nullstack {\r\n\r\n static async getTasks({limit}) {\r\n const json = readFileSync('tasks.json', 'utf-8');\r\n return JSON.parse(json).tasks.slice(0, limit);\r\n }\r\n\r\n prepare(context) {\r\n context.tasks = [];\r\n }\r\n\r\n async initiate(context) {\r\n context.tasks = await this.getTasks({limit: 10});\r\n }\r\n\r\n render() {\r\n return (\r\n <main>\r\n <TaskList route=\"/tasks\" />\r\n <a href=\"/tasks\" route=\"*\"> List Tasks </a>\r\n </main>\r\n )\r\n }\r\n\r\n}\r\n\r\nexport default Application;"},"TaskList":{"html":"import Nullstack from 'nullstack';\r\n\r\nclass TaskList extends Nullstack {\r\n\r\n hideComplete = false;\r\n\r\n renderTask({task}) {\r\n if(this.hideComplete && task.complete) return false;\r\n return (\r\n <li> \r\n <input bind={task.description} />\r\n </li>\r\n )\r\n }\r\n \r\n render({tasks}) {\r\n return (\r\n <div> \r\n <ul>\r\n {tasks.map((task) => <Task task={task} />)}\r\n </ul>\r\n <button onclick={{hideComplete: !this.hideComplete}}>\r\n Toggle complete tasks\r\n </button>\r\n </div>\r\n )\r\n }\r\n\r\n}\r\n\r\nexport default TaskList;"},"Stateful":{"html":"import Nullstack from 'nullstack';\r\n\r\nclass Controlled extends Nullstack {\r\n\r\n count = 0;\r\n\r\n increment({delta}) {\r\n this.count += delta;\r\n }\r\n \r\n render() {\r\n return (\r\n <div>\r\n <button onclick={this.increment} delta={-1}> \r\n {this.count}\r\n </button>\r\n <span> {this.count} </span>\r\n <button onclick={this.increment} delta={1}> \r\n {this.count}\r\n </button>\r\n </div>\r\n )\r\n }\r\n\r\n}\r\n\r\nexport default Controlled;"},"Binding":{"html":"import Nullstack from 'nullstack';\r\n\r\nclass Binding extends Nullstack {\r\n\r\n number = 1;\r\n boolean = true;\r\n \r\n object = {number: 1};\r\n array = ['a', 'b', 'c'];\r\n \r\n render({params}) {\r\n return (\r\n <form>\r\n <input bind={this.number} />\r\n <input bind={this.boolean} type=\"checkbox\" />\r\n <input bind={this.object.number} />\r\n {this.array.map((value, index) => (\r\n <input bind={this.array[index]} />\r\n ))}\r\n <input bind={params.page} />\r\n </form>\r\n )\r\n }\r\n\r\n}\r\n\r\nexport default Binding;"},"Routes":{"html":"import Nullstack from 'nullstack';\r\n\r\nclass Routes extends Nullstack {\r\n\r\n renderPost({params}) {\r\n return (\r\n <div>\r\n <div route=\"/post/getting-started\">\r\n npx create-nullstack-app name\r\n </div>\r\n <div route=\"*\"> {params.slug} </div>\r\n </div>\r\n )\r\n }\r\n \r\n render() {\r\n return (\r\n <div> \r\n <Post route=\"/post/:slug\" />\r\n <a href=\"/post/hello-world\"> Welcome </a>\r\n </div>\r\n )\r\n }\r\n\r\n}\r\n\r\nexport default Routes;"},"Lifecycle":{"html":"import Nullstack from 'nullstack';\r\n\r\nclass Lifecycle extends Nullstack {\r\n \r\n prepare({environment}) {\r\n const {server, client} = environment;\r\n }\r\n\r\n async initiate({environment}) {\r\n const {server, client} = environment;\r\n }\r\n\r\n async hydrate({environment}) {\r\n const {client} = environment;\r\n }\r\n\r\n async update({environment}) {\r\n const {client} = environment;\r\n }\r\n\r\n async terminate({environment}) {\r\n const {client} = environment;\r\n }\r\n\r\n}\r\n\r\nexport default Lifecycle;"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Nullstack - Full-stack Javascript Components","description":"Nullstack is a full-stack javascript framework for building progressive web applications","priority":1}} \ No newline at end of file diff --git a/docs/instance-key/index.html b/docs/instance-key/index.html deleted file mode 100644 index a7cfee2e..00000000 --- a/docs/instance-key/index.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - - Instance Key - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Instance Key

    The instance key is a string in the framework store part of your context and allows you to persist the instance when it moves in the dom.

    -

    This key is readonly after you assign the attribute and available only in the client context.

    -

    You can declare one key per instance.

    -
    -

    💡 If you do not declare a key nullstack will generate one based on dom depth.

    -
    -
    -

    🔥 Keys cannot start with "_." to avoid conflicts with Nullstack generated keys

    -
    -

    Keys must be globally unique since the component could move anywhere around the dom and not only between its siblings.

    -

    Preserving state

    Keys are useful to preserve state in stateful components when you move them in the dom.

    -

    This is especially useful for dynamically sized lists that invoke components.

    -
    import Nullstack from 'nullstack';
    -import Item from './Item';
    -
    -class List extends Nullstack {
    -
    -  // ...
    -
    -  async initiate() {
    -    this.items = await this.getItems();
    -  }
    - 
    -  render({self}) {
    -    return (
    -      <ul> 
    -        {this.items.map((item) => (
    -          <Item key={item.id} {...item} />
    -        ))}
    -      </ul>
    -    )
    -  }
    -
    -}
    -
    -export default Page;
    -
    -

    Shared Instances

    You can also use keys to share the instance between two elements.

    -

    Only the first encounter of the key will run its lifecycle

    -
    import Nullstack from 'nullstack';
    -
    -class Counter extends Nullstack {
    -
    -  count = 0;
    -
    -  render({amount}) {
    -    return (
    -      <div>
    -        <button onclick={{count: this.count+1}}>
    -          {this.count} x {amount} = {this.count * amount}
    -        </button>  
    -      </div>
    -    )
    -  }
    -
    -}
    -
    -export default Counter;
    -
    -
    import Nullstack from 'nullstack';
    -import Counter from './Counter';
    -
    -class Application extends Nullstack {
    -
    -  render() {
    -    return (
    -      <main>
    -        <Counter key="a" amount={1} />
    -        <Counter key="b" amount={2} />
    -        <Counter key="b" amount={3} />
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -export default Application;
    -
    -

    Next step

    ⚔ Learn about the server request and response.

    -
    - - - \ No newline at end of file diff --git a/docs/instance-key/index.json b/docs/instance-key/index.json deleted file mode 100644 index b3a7865d..00000000 --- a/docs/instance-key/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Instance Key","html":"

    The instance key is a string in the framework store part of your context and allows you to persist the instance when it moves in the dom.

    \n

    This key is readonly after you assign the attribute and available only in the client context.

    \n

    You can declare one key per instance.

    \n
    \n

    💡 If you do not declare a key nullstack will generate one based on dom depth.

    \n
    \n
    \n

    🔥 Keys cannot start with "_." to avoid conflicts with Nullstack generated keys

    \n
    \n

    Keys must be globally unique since the component could move anywhere around the dom and not only between its siblings.

    \n

    Preserving state

    Keys are useful to preserve state in stateful components when you move them in the dom.

    \n

    This is especially useful for dynamically sized lists that invoke components.

    \n
    import Nullstack from 'nullstack';\nimport Item from './Item';\n\nclass List extends Nullstack {\n\n  // ...\n\n  async initiate() {\n    this.items = await this.getItems();\n  }\n \n  render({self}) {\n    return (\n      <ul> \n        {this.items.map((item) => (\n          <Item key={item.id} {...item} />\n        ))}\n      </ul>\n    )\n  }\n\n}\n\nexport default Page;\n
    \n

    Shared Instances

    You can also use keys to share the instance between two elements.

    \n

    Only the first encounter of the key will run its lifecycle

    \n
    import Nullstack from 'nullstack';\n\nclass Counter extends Nullstack {\n\n  count = 0;\n\n  render({amount}) {\n    return (\n      <div>\n        <button onclick={{count: this.count+1}}>\n          {this.count} x {amount} = {this.count * amount}\n        </button>  \n      </div>\n    )\n  }\n\n}\n\nexport default Counter;\n
    \n
    import Nullstack from 'nullstack';\nimport Counter from './Counter';\n\nclass Application extends Nullstack {\n\n  render() {\n    return (\n      <main>\n        <Counter key=\"a\" amount={1} />\n        <Counter key=\"b\" amount={2} />\n        <Counter key=\"b\" amount={3} />\n      </main>\n    )\n  }\n\n}\n\nexport default Application;\n
    \n

    Next step

    ⚔ Learn about the server request and response.

    \n","description":"The instance key is a string in the framework store part of your context and allows you to persist the instance when it moves in the dom"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Instance Key - Nullstack","description":"The instance key is a string in the framework store part of your context and allows you to persist the instance when it moves in the dom"}} \ No newline at end of file diff --git a/docs/instance-self/index.html b/docs/instance-self/index.html deleted file mode 100644 index e893ebff..00000000 --- a/docs/instance-self/index.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - Instance Self - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Instance Self

    The self object is a proxy in the framework store part of your context and gives you information about the instance lifecycle.

    -

    This key is readonly and available only in the client context.

    -

    Each instance receives its own self object.

    -

    The following keys are available in the object:

    -
      -
    • initiated: boolean
    • -
    • hydrated: boolean
    • -
    • prerendered: boolean
    • -
    • element: HTMLElement
    • -
    -

    When a lifecycle method is resolved, even if not declared, an equivalent key is set to true in self.

    -

    If the component was server-side rendered the prerendered key will remain true until it is terminated.

    -

    The element key points to the DOM selector and is only guaranteed to exist when hydrate is being called since prepare and initiate could run in the server.

    -
    -

    💡 Do not use element to guess the environment, instead use the environment for that.

    -
    -

    Observing self is a nice way to avoid giving placeholder information to the end-user.

    -
    import Nullstack from 'nullstack';
    -
    -class Page extends Nullstack {
    -
    -  // ...
    -
    -  async initiate() {
    -    this.price = await this.getPrice();
    -  }
    -
    -  async hydrate({self}) {
    -    self.element.querySelector('input').focus();
    -  }
    - 
    -  render({self}) {
    -    if(!self.prerendered && !self.initiated) return false;
    -    return (
    -      <form> 
    -        <input type="number" bind={this.price} />
    -        <button disabled={!self.hydrated}> 
    -          Save
    -        </button>
    -      </form>
    -    )
    -  }
    -
    -}
    -
    -export default Page;
    -
    -
    -

    💡 Components that get optimized into functional components have no access to self.

    -
    -

    Next step

    ⚔ Learn about the instance key.

    -
    - - - \ No newline at end of file diff --git a/docs/instance-self/index.json b/docs/instance-self/index.json deleted file mode 100644 index dcac56f5..00000000 --- a/docs/instance-self/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Instance Self","html":"

    The self object is a proxy in the framework store part of your context and gives you information about the instance lifecycle.

    \n

    This key is readonly and available only in the client context.

    \n

    Each instance receives its own self object.

    \n

    The following keys are available in the object:

    \n
      \n
    • initiated: boolean
    • \n
    • hydrated: boolean
    • \n
    • prerendered: boolean
    • \n
    • element: HTMLElement
    • \n
    \n

    When a lifecycle method is resolved, even if not declared, an equivalent key is set to true in self.

    \n

    If the component was server-side rendered the prerendered key will remain true until it is terminated.

    \n

    The element key points to the DOM selector and is only guaranteed to exist when hydrate is being called since prepare and initiate could run in the server.

    \n
    \n

    💡 Do not use element to guess the environment, instead use the environment for that.

    \n
    \n

    Observing self is a nice way to avoid giving placeholder information to the end-user.

    \n
    import Nullstack from 'nullstack';\n\nclass Page extends Nullstack {\n\n  // ...\n\n  async initiate() {\n    this.price = await this.getPrice();\n  }\n\n  async hydrate({self}) {\n    self.element.querySelector('input').focus();\n  }\n \n  render({self}) {\n    if(!self.prerendered && !self.initiated) return false;\n    return (\n      <form> \n        <input type=\"number\" bind={this.price} />\n        <button disabled={!self.hydrated}> \n          Save\n        </button>\n      </form>\n    )\n  }\n\n}\n\nexport default Page;\n
    \n
    \n

    💡 Components that get optimized into functional components have no access to self.

    \n
    \n

    Next step

    ⚔ Learn about the instance key.

    \n","description":"The self object is a proxy in the framework store part of your context and gives you information about the instance lifecycle"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Instance Self - Nullstack","description":"The self object is a proxy in the framework store part of your context and gives you information about the instance lifecycle"}} \ No newline at end of file diff --git a/docs/manifest-f0cee0769c64977a287dddeaae84c7f6.json b/docs/manifest-f0cee0769c64977a287dddeaae84c7f6.json deleted file mode 100644 index 3b29c313..00000000 --- a/docs/manifest-f0cee0769c64977a287dddeaae84c7f6.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"Nullstack","short_name":"Nullstack","theme_color":"#d22365","background_color":"#2d3748","display":"standalone","orientation":"portrait","scope":"/","start_url":"/","icons":[{"src":"/icon-72x72.png","sizes":"72x72","type":"image/png","purpose":"maskable any"},{"src":"/icon-96x96.png","sizes":"96x96","type":"image/png","purpose":"maskable any"},{"src":"/icon-128x128.png","sizes":"128x128","type":"image/png","purpose":"maskable any"},{"src":"/icon-144x144.png","sizes":"144x144","type":"image/png","purpose":"maskable any"},{"src":"/icon-152x152.png","sizes":"152x152","type":"image/png","purpose":"maskable any"},{"src":"/icon-180x180.png","sizes":"180x180","type":"image/png","purpose":"maskable any"},{"src":"/icon-192x192.png","sizes":"192x192","type":"image/png","purpose":"maskable any"},{"src":"/icon-384x384.png","sizes":"384x384","type":"image/png","purpose":"maskable any"},{"src":"/icon-512x512.png","sizes":"512x512","type":"image/png","purpose":"maskable any"}],"splash_pages":null} \ No newline at end of file diff --git a/docs/njs-file-extension/index.html b/docs/njs-file-extension/index.html deleted file mode 100644 index 7e8b03ba..00000000 --- a/docs/njs-file-extension/index.html +++ /dev/null @@ -1,212 +0,0 @@ - - - - - - - NJS File Extension - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    NJS File Extension

    Nullstack Javascript files let Webpack know which loaders to use at transpile time.

    -

    NJS files must import Nullstack or one of its subclasses.

    -

    If only a subclass is imported, a Nullstack import will be injected at transpile time.

    -

    At transpile time JSX tags will be replaced with Nullstack.element

    -

    This extension also allows Nullstack to make free transpile time optimizations like source injection and extracting renderable components into stateless functions.

    -
    -

    🔥 Each file must have only one class declaration.

    -
    -

    On the server bundle static async functions are mapped into a registry for security.

    -

    On the client bundle static async functions are removed and replaced with a flag.

    -

    On the client bundle static async functions with the name starting with "start" (and optionally followed by an uppercase letter) are completely removed.

    -

    On both server and client bundles, a hash with the md5 of the original source code is added to the class.

    -
    -

    🐱‍💻 Bellow an example of a original .njs file.

    -
    -
    import List from './List';
    -import {readFileSync} from 'fs';
    -
    -class Tasks extends List {
    -
    -  static async getTasks({limit}) {
    -    const json = readFileSync('tasks.json', 'utf-8');
    -    return JSON.parse(json).tasks.slice(0, limit);
    -  }
    -
    -  prepare(context) {
    -    context.tasks = [];
    -  }
    -
    -  async initiate(context) {
    -    context.tasks = await this.getTasks({limit: 10});
    -  }
    -
    -  renderTask({task}) {
    -    return (
    -      <li> 
    -        <input bind={task.description} />
    -      </li>
    -    )
    -  }
    -
    -  render() {
    -    return (
    -      <main>
    -        <ul>
    -          {tasks.map((task) => <Task task={task} />)}
    -        </ul>
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -export default Tasks;
    -
    -
    -

    🐱‍💻 Bellow an example of the same transpiled .njs file.

    -
    -
    import Nullstack from 'nullstack';
    -import List from './List';
    -
    -class Tasks extends List {
    -
    -  static hash = 'd493ac09d0d57574a30f136d31da455f';
    -
    -  static getTasks = true;
    -
    -  prepare(context) {
    -    context.tasks = [];
    -  }
    -
    -  async initiate(context) {
    -    context.tasks = await this.getTasks({limit: 10});
    -  }
    -
    -  renderTask({task}) {
    -    return (
    -      <li> 
    -        <input source={task} bind="description" />
    -      </li>
    -    )
    -  }
    -
    -  render() {
    -    const Task = this.renderTask;
    -    return (
    -      <main>
    -        <ul>
    -          {tasks.map((task) => <Task task={task} />)}
    -        </ul>
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -export default Tasks;
    -
    -
    -

    🐱‍💻 Bellow an example of a original .njs file.

    -
    -
    import Nullstack from 'nullstack';
    -
    -class Tasks extends Nullstack {
    -
    -  renderTask({task}) {
    -    return (
    -      <li> 
    -        <input bind={task.description} />
    -      </li>
    -    )
    -  }
    -
    -  render({tasks}) {
    -    return (
    -      <main>
    -        <ul>
    -          {tasks.map((task) => <Task task={task} />)}
    -        </ul>
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -export default Tasks;
    -
    -
    -

    🐱‍💻 Bellow an example of the same transpiled .njs file.

    -
    -
    import Nullstack from 'nullstack';
    -
    -class Tasks extends Nullstack {
    -
    -  static renderTask({task}) {
    -    return (
    -      <li> 
    -        <input source={task} bind="description" />
    -      </li>
    -    )
    -  }
    -
    -  static render({tasks}) {
    -    const Task = this.renderTask;
    -    return (
    -      <main>
    -        <ul>
    -          {tasks.map((task) => <Task task={task} />)}
    -        </ul>
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -export default Tasks;
    -
    -

    Next step

    ⚔ Learn about server-side rendering.

    -
    - - - \ No newline at end of file diff --git a/docs/njs-file-extension/index.json b/docs/njs-file-extension/index.json deleted file mode 100644 index dc1a25b7..00000000 --- a/docs/njs-file-extension/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"NJS File Extension","html":"

    Nullstack Javascript files let Webpack know which loaders to use at transpile time.

    \n

    NJS files must import Nullstack or one of its subclasses.

    \n

    If only a subclass is imported, a Nullstack import will be injected at transpile time.

    \n

    At transpile time JSX tags will be replaced with Nullstack.element

    \n

    This extension also allows Nullstack to make free transpile time optimizations like source injection and extracting renderable components into stateless functions.

    \n
    \n

    🔥 Each file must have only one class declaration.

    \n
    \n

    On the server bundle static async functions are mapped into a registry for security.

    \n

    On the client bundle static async functions are removed and replaced with a flag.

    \n

    On the client bundle static async functions with the name starting with "start" (and optionally followed by an uppercase letter) are completely removed.

    \n

    On both server and client bundles, a hash with the md5 of the original source code is added to the class.

    \n
    \n

    🐱‍💻 Bellow an example of a original .njs file.

    \n
    \n
    import List from './List';\nimport {readFileSync} from 'fs';\n\nclass Tasks extends List {\n\n  static async getTasks({limit}) {\n    const json = readFileSync('tasks.json', 'utf-8');\n    return JSON.parse(json).tasks.slice(0, limit);\n  }\n\n  prepare(context) {\n    context.tasks = [];\n  }\n\n  async initiate(context) {\n    context.tasks = await this.getTasks({limit: 10});\n  }\n\n  renderTask({task}) {\n    return (\n      <li> \n        <input bind={task.description} />\n      </li>\n    )\n  }\n\n  render() {\n    return (\n      <main>\n        <ul>\n          {tasks.map((task) => <Task task={task} />)}\n        </ul>\n      </main>\n    )\n  }\n\n}\n\nexport default Tasks;\n
    \n
    \n

    🐱‍💻 Bellow an example of the same transpiled .njs file.

    \n
    \n
    import Nullstack from 'nullstack';\nimport List from './List';\n\nclass Tasks extends List {\n\n  static hash = 'd493ac09d0d57574a30f136d31da455f';\n\n  static getTasks = true;\n\n  prepare(context) {\n    context.tasks = [];\n  }\n\n  async initiate(context) {\n    context.tasks = await this.getTasks({limit: 10});\n  }\n\n  renderTask({task}) {\n    return (\n      <li> \n        <input source={task} bind=\"description\" />\n      </li>\n    )\n  }\n\n  render() {\n    const Task = this.renderTask;\n    return (\n      <main>\n        <ul>\n          {tasks.map((task) => <Task task={task} />)}\n        </ul>\n      </main>\n    )\n  }\n\n}\n\nexport default Tasks;\n
    \n
    \n

    🐱‍💻 Bellow an example of a original .njs file.

    \n
    \n
    import Nullstack from 'nullstack';\n\nclass Tasks extends Nullstack {\n\n  renderTask({task}) {\n    return (\n      <li> \n        <input bind={task.description} />\n      </li>\n    )\n  }\n\n  render({tasks}) {\n    return (\n      <main>\n        <ul>\n          {tasks.map((task) => <Task task={task} />)}\n        </ul>\n      </main>\n    )\n  }\n\n}\n\nexport default Tasks;\n
    \n
    \n

    🐱‍💻 Bellow an example of the same transpiled .njs file.

    \n
    \n
    import Nullstack from 'nullstack';\n\nclass Tasks extends Nullstack {\n\n  static renderTask({task}) {\n    return (\n      <li> \n        <input source={task} bind=\"description\" />\n      </li>\n    )\n  }\n\n  static render({tasks}) {\n    const Task = this.renderTask;\n    return (\n      <main>\n        <ul>\n          {tasks.map((task) => <Task task={task} />)}\n        </ul>\n      </main>\n    )\n  }\n\n}\n\nexport default Tasks;\n
    \n

    Next step

    ⚔ Learn about server-side rendering.

    \n","description":"Nullstack Javascript files let Webpack know which loaders to use at transpile time"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"NJS File Extension - Nullstack","description":"Nullstack Javascript files let Webpack know which loaders to use at transpile time"}} \ No newline at end of file diff --git a/docs/nullachan.png b/docs/nullachan.png deleted file mode 100644 index 0f5ccd45..00000000 Binary files a/docs/nullachan.png and /dev/null differ diff --git a/docs/nullstack.png b/docs/nullstack.png deleted file mode 100644 index a3fe1254..00000000 Binary files a/docs/nullstack.png and /dev/null differ diff --git a/docs/nullstack.svg b/docs/nullstack.svg deleted file mode 100644 index edd08d14..00000000 --- a/docs/nullstack.svg +++ /dev/null @@ -1 +0,0 @@ -logo-nullstack \ No newline at end of file diff --git a/docs/offline-f0cee0769c64977a287dddeaae84c7f6/index.html b/docs/offline-f0cee0769c64977a287dddeaae84c7f6/index.html deleted file mode 100644 index 6e8380e1..00000000 --- a/docs/offline-f0cee0769c64977a287dddeaae84c7f6/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - Not Found - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/offline-f0cee0769c64977a287dddeaae84c7f6/index.json b/docs/offline-f0cee0769c64977a287dddeaae84c7f6/index.json deleted file mode 100644 index 58e8d10a..00000000 --- a/docs/offline-f0cee0769c64977a287dddeaae84c7f6/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"","html":""},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Not Found - Nullstack","description":"Sorry, this is not the page you are looking for."}} \ No newline at end of file diff --git a/docs/renderable-components/index.html b/docs/renderable-components/index.html deleted file mode 100644 index b142f335..00000000 --- a/docs/renderable-components/index.html +++ /dev/null @@ -1,315 +0,0 @@ - - - - - - - Renderable Components - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Renderable Components

    The simplest component you can make is a renderable component.

    -

    Renderable components are very similar to web components, they give you the ability to create new HTML tags that shortcut a group of other HTML tags.

    -

    Create a file in your src folder with the name of your component and the njs extension.

    -

    In this example it is going to be called HelloWorld.njs.

    -

    All you have to do is to import Nullstack or any of its subclasses and extend your class from it, define an instance method called render that returns any JSX, and export the component.

    -
    -

    ✨ Install the official Nullstack VSCode Extension to generate classes with a snippet.

    -
    -
    import Nullstack from 'nullstack';
    -
    -class HelloWorld extends Nullstack {
    - 
    -  render() {
    -    return (
    -      <div> Hello World </div>
    -    )
    -  }
    -
    -}
    -
    -export default HelloWorld;
    -
    -

    The code above is just declaring the component, you still have to use it.

    -

    Importing the component in your application gives you the ability to use a new tag in your render.

    -

    This tag will be replaced with whatever you returned in your component render.

    -
    import Nullstack from 'nullstack';
    -
    -import './Application.scss';
    -
    -import HelloWorld from './HelloWorld';
    -
    -class Application extends Nullstack {
    -
    -  // ...
    -
    -  render({page}) {
    -    return (
    -      <main>
    -        <h1> {page.title} </h1>
    -        <a href="https://nullstack.app/documentation" target="_blank"> Read the documentation </a>
    -        <HelloWorld />
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -export default Application;
    -
    -
    -

    💡 Components that do nothing besides rendering are extracted into faster functional components at transpile time!

    -
    -

    Using HTML attributes

    Nullstack JSX deviates a little from the spec.

    -

    You can use the normal HTML attributes like class and for directly.

    -
    <label for="input" class="dont-label-me"> I am a label </label>
    -
    -

    Headless components

    If you want to skip rendering the component at all you can simply return false from the render.

    -
    import Nullstack from 'nullstack';
    -
    -class Headless extends Nullstack {
    - 
    -  render() {
    -    return false;
    -  }
    -
    -}
    -
    -export default Headless;
    -
    -

    This will allocate DOM space for when you decide to render markup there.

    -

    This is also useful for conditional rendering.

    -

    If all you want to do is to generate an invisible component you can skip defining the render method at all.

    -

    Inner components

    Instead of creating a new component just to organize code-splitting, you can create an inner component.

    -

    Inner components are any method that the name starts with render followed by an uppercase character.

    -

    Inner components share the same instance and scope as the main component, therefore, are very convenient to avoid problems like props drilling.

    -

    To invoke the inner component use a JSX tag with the method name without the render prefix.

    -
    import Nullstack from 'nullstack';
    -
    -class Post extends Nullstack {
    -
    -  renderArticle() {
    -    return (
    -      <article> Content </article>
    -    )
    -  }
    -
    -  renderAside() {
    -    return (
    -      <aside> Related content </aside>
    -    )
    -  }
    - 
    -  render() {
    -    return (
    -      <div>
    -        <Article />
    -        <Aside />
    -      </div>
    -    )
    -  }
    -
    -}
    -
    -export default HelloWorld;
    -
    -
    -

    💡 Nullstack will inject a constant reference to the function at transpile time in order to completely skip the runtime lookup process!

    -
    -

    Boolean attributes

    Attributes can be assigned as a boolean.

    -

    When the value is false the attribute will not be rendered at all.

    -

    When the value is true it will be rendered as a boolean attribute without a string value.

    -
    <button disabled={false}> Button </button>
    -
    -

    You can shortcut attributes when you know the value will always be true.

    -
    <button disabled> Button </button>
    -
    -
    -

    ✨ Learn more about attributes.

    -
    -

    Element tag

    If you need to decide the tag name at runtime, you can use the element tag and set the tag attribute conditionally.

    -
    <element tag={!!link ? 'a' : 'span'} href={link || false}>
    -  some arbitrary text
    -</element>
    -
    -

    When the tag attribute is omitted, Nullstack will default to a div.

    -

    SVG Elements

    SVG can be used as if it were any regular HTML tag.

    -

    You can manipulate the SVG using attributes and events normally.

    -
    <svg height={this.size} viewBox="0 0 100 100">
    -  <circle cx="50" cy="50" r="40" onclick={this.grow} />
    -</svg> 
    -
    -
    -

    ✨ Learn more about events.

    -
    -

    Components with children

    Your component can be invoked passing a block of content.

    -
    <Header> 
    -  <h1> Hello World </h1>
    -</Header>
    -
    -

    This doesn't automatically render the block since it wouldn't know where to place it.

    -

    You can destructure the children on the render method and place it in your markup.

    -
    import Nullstack from 'nullstack';
    -
    -class Header extends Nullstack {
    - 
    -  render({children}) {
    -    return (
    -      <div>{children}</div>
    -    )
    -  }
    -
    -}
    -
    -export default Header;
    -
    -
    -

    ✨ This is possible because the children key is part of the instance context.

    -
    -

    Lists

    You can map over lists without declaring a key.

    -

    Lists that may change length must be wrapped in a parent element just for them.

    -
    <ul>
    -  {list.map((item) => <li>{item.name}</li>)}
    -</ul>
    -
    -

    You can emulate a fixed-size list by returning false instead of an element to reserve dom space.

    -
    {list.map((item) => (
    -  item.visible ? <div>{item.name}</div> : false
    -)}
    -
    -

    It's a nice practice to use inner components combined with lists to clean up your code.

    -
    import Nullstack from 'nullstack';
    -
    -class List extends Nullstack {
    -
    -  items = [
    -    {visible: true, number: 1},
    -    {visible: false, number: 2},
    -    {visible: true, number: 3}
    -  ]
    -
    -  renderItem({visible, number}) {
    -    if(!visible) return false;
    -    return (
    -      <li> {number} </li>
    -    )
    -  }
    - 
    -  render() {
    -    return (
    -      <ul>
    -        {this.items.map((item) => <Item {...item} />)}
    -      </ul>
    -    )
    -  }
    -
    -}
    -
    -export default List;
    -
    -
    -

    ✨ Sometimes you will notice keys in the map. Learn more about the instance key.

    -
    -

    Inner HTML

    You can set the inner HTML of an element with the html attribute.

    -

    Links inside the HTML string will be replaced with routable anchors.

    -
    import Nullstack from 'nullstack';
    -
    -class Post extends Nullstack {
    -
    -  content = `
    -    <h1> This is a Post </h1>
    -    <a href="/other-post">
    -      Check this other post
    -    </a>
    -  `;
    - 
    -  render() {
    -    return (
    -      <article html={this.content} />
    -    )
    -  }
    -
    -}
    -
    -export default Post;
    -
    -
    -

    🔥 Be careful! When using user-generated HTML you are in risk of script injection

    -
    -

    The head tag

    Renderable components can render inside the head tag an unlimited number of times at any depth of the application.

    -

    The head tag will only be updated during the server-side rendering process and changes will be ignored after the hydration process.

    -
    import Nullstack from 'nullstack';
    -
    -class Application extends Nullstack {
    -
    -  // ...
    -
    -  render() {
    -    return (
    -      <main>
    -        <div>
    -          <head>
    -            <link rel="preconnect" href="https://www.googletagmanager.com" />
    -          </head>
    -        </div>
    -        <head>
    -          <link rel="preload" href="/roboto-v20-latin-300.woff2" as="font" type="font/woff2" crossorigin />
    -          <link rel="preload" href="/crete-round-v9-latin-regular.woff2" as="font" type="font/woff2" crossorigin />
    -        </head>
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -export default Application;
    -
    -
    -

    🔥 you should not use the head tag to update metatags that Nullstack already controls

    -
    -

    Caveats

    Currently, Nullstack doesn't support JSX Fragments. If you want to see this feature implemented please open an issue on github.

    -

    Next step

    ⚔ Add state to your component using stateful components.

    -
    - - - \ No newline at end of file diff --git a/docs/renderable-components/index.json b/docs/renderable-components/index.json deleted file mode 100644 index 08b46537..00000000 --- a/docs/renderable-components/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Renderable Components","html":"

    The simplest component you can make is a renderable component.

    \n

    Renderable components are very similar to web components, they give you the ability to create new HTML tags that shortcut a group of other HTML tags.

    \n

    Create a file in your src folder with the name of your component and the njs extension.

    \n

    In this example it is going to be called HelloWorld.njs.

    \n

    All you have to do is to import Nullstack or any of its subclasses and extend your class from it, define an instance method called render that returns any JSX, and export the component.

    \n
    \n

    ✨ Install the official Nullstack VSCode Extension to generate classes with a snippet.

    \n
    \n
    import Nullstack from 'nullstack';\n\nclass HelloWorld extends Nullstack {\n \n  render() {\n    return (\n      <div> Hello World </div>\n    )\n  }\n\n}\n\nexport default HelloWorld;\n
    \n

    The code above is just declaring the component, you still have to use it.

    \n

    Importing the component in your application gives you the ability to use a new tag in your render.

    \n

    This tag will be replaced with whatever you returned in your component render.

    \n
    import Nullstack from 'nullstack';\n\nimport './Application.scss';\n\nimport HelloWorld from './HelloWorld';\n\nclass Application extends Nullstack {\n\n  // ...\n\n  render({page}) {\n    return (\n      <main>\n        <h1> {page.title} </h1>\n        <a href=\"https://nullstack.app/documentation\" target=\"_blank\"> Read the documentation </a>\n        <HelloWorld />\n      </main>\n    )\n  }\n\n}\n\nexport default Application;\n
    \n
    \n

    💡 Components that do nothing besides rendering are extracted into faster functional components at transpile time!

    \n
    \n

    Using HTML attributes

    Nullstack JSX deviates a little from the spec.

    \n

    You can use the normal HTML attributes like class and for directly.

    \n
    <label for=\"input\" class=\"dont-label-me\"> I am a label </label>\n
    \n

    Headless components

    If you want to skip rendering the component at all you can simply return false from the render.

    \n
    import Nullstack from 'nullstack';\n\nclass Headless extends Nullstack {\n \n  render() {\n    return false;\n  }\n\n}\n\nexport default Headless;\n
    \n

    This will allocate DOM space for when you decide to render markup there.

    \n

    This is also useful for conditional rendering.

    \n

    If all you want to do is to generate an invisible component you can skip defining the render method at all.

    \n

    Inner components

    Instead of creating a new component just to organize code-splitting, you can create an inner component.

    \n

    Inner components are any method that the name starts with render followed by an uppercase character.

    \n

    Inner components share the same instance and scope as the main component, therefore, are very convenient to avoid problems like props drilling.

    \n

    To invoke the inner component use a JSX tag with the method name without the render prefix.

    \n
    import Nullstack from 'nullstack';\n\nclass Post extends Nullstack {\n\n  renderArticle() {\n    return (\n      <article> Content </article>\n    )\n  }\n\n  renderAside() {\n    return (\n      <aside> Related content </aside>\n    )\n  }\n \n  render() {\n    return (\n      <div>\n        <Article />\n        <Aside />\n      </div>\n    )\n  }\n\n}\n\nexport default HelloWorld;\n
    \n
    \n

    💡 Nullstack will inject a constant reference to the function at transpile time in order to completely skip the runtime lookup process!

    \n
    \n

    Boolean attributes

    Attributes can be assigned as a boolean.

    \n

    When the value is false the attribute will not be rendered at all.

    \n

    When the value is true it will be rendered as a boolean attribute without a string value.

    \n
    <button disabled={false}> Button </button>\n
    \n

    You can shortcut attributes when you know the value will always be true.

    \n
    <button disabled> Button </button>\n
    \n
    \n

    ✨ Learn more about attributes.

    \n
    \n

    Element tag

    If you need to decide the tag name at runtime, you can use the element tag and set the tag attribute conditionally.

    \n
    <element tag={!!link ? 'a' : 'span'} href={link || false}>\n  some arbitrary text\n</element>\n
    \n

    When the tag attribute is omitted, Nullstack will default to a div.

    \n

    SVG Elements

    SVG can be used as if it were any regular HTML tag.

    \n

    You can manipulate the SVG using attributes and events normally.

    \n
    <svg height={this.size} viewBox=\"0 0 100 100\">\n  <circle cx=\"50\" cy=\"50\" r=\"40\" onclick={this.grow} />\n</svg> \n
    \n
    \n

    ✨ Learn more about events.

    \n
    \n

    Components with children

    Your component can be invoked passing a block of content.

    \n
    <Header> \n  <h1> Hello World </h1>\n</Header>\n
    \n

    This doesn't automatically render the block since it wouldn't know where to place it.

    \n

    You can destructure the children on the render method and place it in your markup.

    \n
    import Nullstack from 'nullstack';\n\nclass Header extends Nullstack {\n \n  render({children}) {\n    return (\n      <div>{children}</div>\n    )\n  }\n\n}\n\nexport default Header;\n
    \n
    \n

    ✨ This is possible because the children key is part of the instance context.

    \n
    \n

    Lists

    You can map over lists without declaring a key.

    \n

    Lists that may change length must be wrapped in a parent element just for them.

    \n
    <ul>\n  {list.map((item) => <li>{item.name}</li>)}\n</ul>\n
    \n

    You can emulate a fixed-size list by returning false instead of an element to reserve dom space.

    \n
    {list.map((item) => (\n  item.visible ? <div>{item.name}</div> : false\n)}\n
    \n

    It's a nice practice to use inner components combined with lists to clean up your code.

    \n
    import Nullstack from 'nullstack';\n\nclass List extends Nullstack {\n\n  items = [\n    {visible: true, number: 1},\n    {visible: false, number: 2},\n    {visible: true, number: 3}\n  ]\n\n  renderItem({visible, number}) {\n    if(!visible) return false;\n    return (\n      <li> {number} </li>\n    )\n  }\n \n  render() {\n    return (\n      <ul>\n        {this.items.map((item) => <Item {...item} />)}\n      </ul>\n    )\n  }\n\n}\n\nexport default List;\n
    \n
    \n

    ✨ Sometimes you will notice keys in the map. Learn more about the instance key.

    \n
    \n

    Inner HTML

    You can set the inner HTML of an element with the html attribute.

    \n

    Links inside the HTML string will be replaced with routable anchors.

    \n
    import Nullstack from 'nullstack';\n\nclass Post extends Nullstack {\n\n  content = `\n    <h1> This is a Post </h1>\n    <a href=\"/other-post\">\n      Check this other post\n    </a>\n  `;\n \n  render() {\n    return (\n      <article html={this.content} />\n    )\n  }\n\n}\n\nexport default Post;\n
    \n
    \n

    🔥 Be careful! When using user-generated HTML you are in risk of script injection

    \n
    \n

    The head tag

    Renderable components can render inside the head tag an unlimited number of times at any depth of the application.

    \n

    The head tag will only be updated during the server-side rendering process and changes will be ignored after the hydration process.

    \n
    import Nullstack from 'nullstack';\n\nclass Application extends Nullstack {\n\n  // ...\n\n  render() {\n    return (\n      <main>\n        <div>\n          <head>\n            <link rel=\"preconnect\" href=\"https://www.googletagmanager.com\" />\n          </head>\n        </div>\n        <head>\n          <link rel=\"preload\" href=\"/roboto-v20-latin-300.woff2\" as=\"font\" type=\"font/woff2\" crossorigin />\n          <link rel=\"preload\" href=\"/crete-round-v9-latin-regular.woff2\" as=\"font\" type=\"font/woff2\" crossorigin />\n        </head>\n      </main>\n    )\n  }\n\n}\n\nexport default Application;\n
    \n
    \n

    🔥 you should not use the head tag to update metatags that Nullstack already controls

    \n
    \n

    Caveats

    Currently, Nullstack doesn't support JSX Fragments. If you want to see this feature implemented please open an issue on github.

    \n

    Next step

    ⚔ Add state to your component using stateful components.

    \n","description":"Renderable components are very similar to web components they give you the ability to create new HTML tags that shortcut a group of other HTML tags"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Renderable Components - Nullstack","description":"Renderable components are very similar to web components they give you the ability to create new HTML tags that shortcut a group of other HTML tags"}} \ No newline at end of file diff --git a/docs/roboto-v20-latin-300.woff2 b/docs/roboto-v20-latin-300.woff2 deleted file mode 100644 index ef8c8836..00000000 Binary files a/docs/roboto-v20-latin-300.woff2 and /dev/null differ diff --git a/docs/roboto-v20-latin-500.woff2 b/docs/roboto-v20-latin-500.woff2 deleted file mode 100644 index 6362d7f6..00000000 Binary files a/docs/roboto-v20-latin-500.woff2 and /dev/null differ diff --git a/docs/robots.txt b/docs/robots.txt deleted file mode 100644 index 95a840fc..00000000 --- a/docs/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -User-Agent: * -Allow: / -Sitemap: https://nullstack.app/sitemap.xml \ No newline at end of file diff --git a/docs/routes-and-params/index.html b/docs/routes-and-params/index.html deleted file mode 100644 index ae8ce661..00000000 --- a/docs/routes-and-params/index.html +++ /dev/null @@ -1,300 +0,0 @@ - - - - - - - Routes and Params - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Routes and Params

    Nullstack has built-in routes, it would make no sense otherwise since web applications are expected to have hyperlinks.

    -

    Any tag can receive a route attribute, be it a component, inner component, or simple HTML tag.

    -
    import Nullstack from 'nullstack';
    -import Page from './Page';
    -
    -class Application extends Nullstack {
    -
    -  renderHome() {
    -    return (
    -      <section> Home </section>
    -    )
    -  }
    - 
    -  render({count}) {
    -    return (
    -      <main>
    -        <Home route="/" />
    -        <Page route="/page">
    -        <abbr route="/abbreviations"> Abbreviations </abbr>
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -export default Application;
    -
    -

    Links on Nullstack are simple a tags with the href value starting with "/".

    -
    <a href="/page/about"> About Page </a>
    -
    -
    -

    💡 On the client side the click event will push history without reloading the page.

    -
    -
    -

    ✨ You can still assign your own click event to the tag without losing the framework behavior.

    -
    -

    Params

    The params key is an object proxy injected into every client instance.

    -

    Each query string param is mapped to this object.

    -

    By default any key you request from this object will return a string.

    -

    If the value is undefined it will return an empty string.

    -

    If the value is true or false it will return a boolean instead.

    -
    -

    🐱‍💻 Bellow an exemple that visits "/books?expanded=true&page=2":

    -
    -
    import Nullstack from 'nullstack';
    -
    -class Books extends Nullstack {
    -
    -  async initiate({params}) {
    -    if(params.expanded) {
    -      const page = parseInt(params.page) || 1;
    -      this.books = await this.getBooks({page});
    -    }
    -  }
    -
    -}
    -
    -export default Books;
    -
    -

    Assigning to a params key will cause a redirect to the route with updated params.

    -

    When you assign to a param, the value will be converted to JSON before being set.

    -
    -

    💡 Redirects work in batches, so there is no performance loss in multiple assignments.

    -
    -
    import Nullstack from 'nullstack';
    -
    -class Paginator extends Nullstack {
    -
    -  handleClick({params}) {
    -    params.filter = '';
    -    params.page = 1;
    -  }
    -
    -}
    -
    -export default Paginator;
    -
    -

    Assigning an empty string to a param will remove it from the url.

    -

    Dynamic Segments

    Part of the route can be an expression started with ":" followed by a param name.

    -

    This value will be matched against any string in the same directory position.

    -

    The value of the string in the URL will be assigned to the context params and functions below this point in the hierarchy will have access to the new key.

    -
    -

    🐱‍💻 Bellow an example that visits "/category/suspense?page=2":

    -
    -
    import Nullstack from 'nullstack';
    -
    -class Books extends Nullstack {
    -
    -  async initiate({params}) {
    -    const page = parseInt(params.page) || 1;
    -    const category = params.slug;
    -    this.books = await this.getBooks({category, page});
    -  }
    -
    -}
    -
    -export default Books;
    -
    -
    import Nullstack from 'nullstack';
    -import Books from './Books';
    -
    -class Application extends Nullstack {
    -
    -  render() {
    -    <main>
    -      <Books route="/category/:slug">
    -    </main>
    -  }
    -
    -}
    -
    -export default Application;
    -
    -

    When a dynamic segment is changed, as for example moving from "/category/suspense" to "/category/comedy", the component will be terminated and a new instance will be created.

    -

    Changing a query param will not re-instantiate the component.

    -

    Children of the component will not be re-instantiated automatically, you can set the same route to the children or do it manually if you desire this behavior.

    -
    -

    💡 The behavior mentioned above solves many of the problems you have to normally deal with manually.

    -
    -

    Wildcards

    Wildcards are routes declared with "*" as the attribute value.

    -

    These routes will match anything if nothing above it matches the requested URL.

    -
    import Nullstack from 'nullstack';
    -import Home from './Home';
    -
    -class Application extends Nullstack {
    -
    -  render({count}) {
    -    return (
    -      <main>
    -        <Home route="/" />
    -        <div route="*"> Wildcard </abbr>
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -

    Wildcards can be prefixed with a segment.

    -
    -

    ✨ this is especially useful for engines that can be mounted in your application.

    -
    -
    import Nullstack from 'nullstack';
    -import Home from './Home';
    -import BlogEngine from './BlogEngine';
    -
    -class Application extends Nullstack {
    -
    -  render({count}) {
    -    return (
    -      <main>
    -        <Home route="/" />
    -        <BlogEngine route="/blog/*" />
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -

    Router

    The router key is an object proxy injected into every client instance.

    -

    The router has two keys:

    -
      -
    • url
    • -
    • path
    • -
    -

    The url key returns everything after the domain including the path and the query params as a string.

    -

    The path key returns only the path without query params.

    -
    -

    💡 Both keys above automatically remove the trailing slash for convenience.

    -
    -

    Assigning to url or path will cause a redirect.

    -
    -

    💡 Under the hood a tags and params use the router.

    -
    -
    import Nullstack from 'nullstack';
    -
    -class Application extends Nullstack {
    -
    -  prepare({router}) {
    -    if(router.path == '/') {
    -      router.path = '/dashboard';
    -    }
    -  }
    -
    -}
    -
    -

    Custom Events

    Updating router.url or router.path will raise a custom event.

    -
    import Nullstack from 'nullstack';
    -
    -class Analytics extends Nullstack {
    -
    -  hydrate({router}) {
    -    window.addEventListener(router.event, () => {
    -      console.log(router.url);
    -    });
    -  }
    -
    -}
    -
    -export default Analytics;
    -
    -

    Special anchors

    Anchor tags accept some convenient special attributes besides the regular href.

    -

    You can set the params attribute with an object as the value.

    -

    The path will remain the same as the current router path, but the params will be replaced by the new params you specify.

    -
    <a params={{page: 1}}> First Page </a>
    -
    -

    If you wish to just update some params and keep the others, you can use the javascript spread operator for that.

    -
    <a params={{...params, page: 1}}> First Page </a>
    -
    -

    You can set the path attribute with a string starting with "/" and no query params.

    -

    The params will remain the same, but the path will be updated.

    -
    <a path="/category/suspense"> Suspense Books </a>
    -
    -

    Both attributes above can be used at the same time.

    -
    <a path="/category/suspense" params={{...params, page: 1}}> Suspense Books </a>
    -
    -

    Nested routes

    The first route to be matched will be rendered.

    -

    The other elements with a route will not be rendered, however, elements on the same level without a route attribute will render normally.

    -

    The router will lookup for one route per dom depth level, this allows you to have nested routing behavior.

    -
    import Nullstack from 'nullstack';
    -import Home from './Home';
    -
    -class Application extends Nullstack {
    -
    -  renderPage() {
    -    return (
    -      <section>
    -        <div route="/page/about"> About Page </div>
    -        <div route="/page/contact"> Contact Page </div>
    -      </section>
    -    )
    -  }
    - 
    -  render({count}) {
    -    return (
    -      <main>
    -        <Home route="/" />
    -        <Page route="/page/:slug">
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -export default Application;
    -
    -

    Next step

    ⚔ Learn about two-way bindings.

    -
    - - - \ No newline at end of file diff --git a/docs/routes-and-params/index.json b/docs/routes-and-params/index.json deleted file mode 100644 index 3701e5c6..00000000 --- a/docs/routes-and-params/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Routes and Params","html":"

    Nullstack has built-in routes, it would make no sense otherwise since web applications are expected to have hyperlinks.

    \n

    Any tag can receive a route attribute, be it a component, inner component, or simple HTML tag.

    \n
    import Nullstack from 'nullstack';\nimport Page from './Page';\n\nclass Application extends Nullstack {\n\n  renderHome() {\n    return (\n      <section> Home </section>\n    )\n  }\n \n  render({count}) {\n    return (\n      <main>\n        <Home route=\"/\" />\n        <Page route=\"/page\">\n        <abbr route=\"/abbreviations\"> Abbreviations </abbr>\n      </main>\n    )\n  }\n\n}\n\nexport default Application;\n
    \n

    Links

    Links on Nullstack are simple a tags with the href value starting with "/".

    \n
    <a href=\"/page/about\"> About Page </a>\n
    \n
    \n

    💡 On the client side the click event will push history without reloading the page.

    \n
    \n
    \n

    ✨ You can still assign your own click event to the tag without losing the framework behavior.

    \n
    \n

    Params

    The params key is an object proxy injected into every client instance.

    \n

    Each query string param is mapped to this object.

    \n

    By default any key you request from this object will return a string.

    \n

    If the value is undefined it will return an empty string.

    \n

    If the value is true or false it will return a boolean instead.

    \n
    \n

    🐱‍💻 Bellow an exemple that visits "/books?expanded=true&page=2":

    \n
    \n
    import Nullstack from 'nullstack';\n\nclass Books extends Nullstack {\n\n  async initiate({params}) {\n    if(params.expanded) {\n      const page = parseInt(params.page) || 1;\n      this.books = await this.getBooks({page});\n    }\n  }\n\n}\n\nexport default Books;\n
    \n

    Assigning to a params key will cause a redirect to the route with updated params.

    \n

    When you assign to a param, the value will be converted to JSON before being set.

    \n
    \n

    💡 Redirects work in batches, so there is no performance loss in multiple assignments.

    \n
    \n
    import Nullstack from 'nullstack';\n\nclass Paginator extends Nullstack {\n\n  handleClick({params}) {\n    params.filter = '';\n    params.page = 1;\n  }\n\n}\n\nexport default Paginator;\n
    \n

    Assigning an empty string to a param will remove it from the url.

    \n

    Dynamic Segments

    Part of the route can be an expression started with ":" followed by a param name.

    \n

    This value will be matched against any string in the same directory position.

    \n

    The value of the string in the URL will be assigned to the context params and functions below this point in the hierarchy will have access to the new key.

    \n
    \n

    🐱‍💻 Bellow an example that visits "/category/suspense?page=2":

    \n
    \n
    import Nullstack from 'nullstack';\n\nclass Books extends Nullstack {\n\n  async initiate({params}) {\n    const page = parseInt(params.page) || 1;\n    const category = params.slug;\n    this.books = await this.getBooks({category, page});\n  }\n\n}\n\nexport default Books;\n
    \n
    import Nullstack from 'nullstack';\nimport Books from './Books';\n\nclass Application extends Nullstack {\n\n  render() {\n    <main>\n      <Books route=\"/category/:slug\">\n    </main>\n  }\n\n}\n\nexport default Application;\n
    \n

    When a dynamic segment is changed, as for example moving from "/category/suspense" to "/category/comedy", the component will be terminated and a new instance will be created.

    \n

    Changing a query param will not re-instantiate the component.

    \n

    Children of the component will not be re-instantiated automatically, you can set the same route to the children or do it manually if you desire this behavior.

    \n
    \n

    💡 The behavior mentioned above solves many of the problems you have to normally deal with manually.

    \n
    \n

    Wildcards

    Wildcards are routes declared with "*" as the attribute value.

    \n

    These routes will match anything if nothing above it matches the requested URL.

    \n
    import Nullstack from 'nullstack';\nimport Home from './Home';\n\nclass Application extends Nullstack {\n\n  render({count}) {\n    return (\n      <main>\n        <Home route=\"/\" />\n        <div route=\"*\"> Wildcard </abbr>\n      </main>\n    )\n  }\n\n}\n
    \n

    Wildcards can be prefixed with a segment.

    \n
    \n

    ✨ this is especially useful for engines that can be mounted in your application.

    \n
    \n
    import Nullstack from 'nullstack';\nimport Home from './Home';\nimport BlogEngine from './BlogEngine';\n\nclass Application extends Nullstack {\n\n  render({count}) {\n    return (\n      <main>\n        <Home route=\"/\" />\n        <BlogEngine route=\"/blog/*\" />\n      </main>\n    )\n  }\n\n}\n
    \n

    Router

    The router key is an object proxy injected into every client instance.

    \n

    The router has two keys:

    \n
      \n
    • url
    • \n
    • path
    • \n
    \n

    The url key returns everything after the domain including the path and the query params as a string.

    \n

    The path key returns only the path without query params.

    \n
    \n

    💡 Both keys above automatically remove the trailing slash for convenience.

    \n
    \n

    Assigning to url or path will cause a redirect.

    \n
    \n

    💡 Under the hood a tags and params use the router.

    \n
    \n
    import Nullstack from 'nullstack';\n\nclass Application extends Nullstack {\n\n  prepare({router}) {\n    if(router.path == '/') {\n      router.path = '/dashboard';\n    }\n  }\n\n}\n
    \n

    Custom Events

    Updating router.url or router.path will raise a custom event.

    \n
    import Nullstack from 'nullstack';\n\nclass Analytics extends Nullstack {\n\n  hydrate({router}) {\n    window.addEventListener(router.event, () => {\n      console.log(router.url);\n    });\n  }\n\n}\n\nexport default Analytics;\n
    \n

    Special anchors

    Anchor tags accept some convenient special attributes besides the regular href.

    \n

    You can set the params attribute with an object as the value.

    \n

    The path will remain the same as the current router path, but the params will be replaced by the new params you specify.

    \n
    <a params={{page: 1}}> First Page </a>\n
    \n

    If you wish to just update some params and keep the others, you can use the javascript spread operator for that.

    \n
    <a params={{...params, page: 1}}> First Page </a>\n
    \n

    You can set the path attribute with a string starting with "/" and no query params.

    \n

    The params will remain the same, but the path will be updated.

    \n
    <a path=\"/category/suspense\"> Suspense Books </a>\n
    \n

    Both attributes above can be used at the same time.

    \n
    <a path=\"/category/suspense\" params={{...params, page: 1}}> Suspense Books </a>\n
    \n

    Nested routes

    The first route to be matched will be rendered.

    \n

    The other elements with a route will not be rendered, however, elements on the same level without a route attribute will render normally.

    \n

    The router will lookup for one route per dom depth level, this allows you to have nested routing behavior.

    \n
    import Nullstack from 'nullstack';\nimport Home from './Home';\n\nclass Application extends Nullstack {\n\n  renderPage() {\n    return (\n      <section>\n        <div route=\"/page/about\"> About Page </div>\n        <div route=\"/page/contact\"> Contact Page </div>\n      </section>\n    )\n  }\n \n  render({count}) {\n    return (\n      <main>\n        <Home route=\"/\" />\n        <Page route=\"/page/:slug\">\n      </main>\n    )\n  }\n\n}\n\nexport default Application;\n
    \n

    Next step

    ⚔ Learn about two-way bindings.

    \n","description":"Nullstack has built-in routes, it would make no sense otherwise since web applications are expected to have hyperlinks."},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Routes and Params - Nullstack","description":"Nullstack has built-in routes, it would make no sense otherwise since web applications are expected to have hyperlinks."}} \ No newline at end of file diff --git a/docs/server-functions/index.html b/docs/server-functions/index.html deleted file mode 100644 index 4c84f72d..00000000 --- a/docs/server-functions/index.html +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - - Server Functions - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Server Functions

    Server functions are specialized microservices that at transpile time are converted into API entry points.

    -

    To flag a function as a server function, you must declare it as static async.

    -

    Being a static function means it has no access to the instance scope.

    -

    However, instead of calling the static version from the class, you must invoke it as an instance function.

    -

    Server functions can be called anytime in your code and are not limited to prerender steps.

    -
    import Nullstack from 'nullstack';
    -
    -class Component extends Nullstack {
    -
    -  static async increment(context) {
    -    context.count++;
    -  }
    -
    -  async handleClick() {
    -    await this.increment();
    -  }
    -
    -  // ...
    -
    -}
    -
    -export default Component;
    -
    -
    -

    ✨ Learn more about the server context.

    -
    -

    Client behavior

    When you call a server function from the client, the arguments will be serialized as JSON.

    -

    The arguments will be posted against the automatically generated API and merged with the server context when it reaches the server.

    -

    The return value of the server function will be serialized back to the client and can be seamlessly used as if it were a local function.

    -
    import Nullstack from 'nullstack';
    -
    -class Component extends Nullstack {
    -
    -  static async increment(context) {
    -    context.count++;
    -    return context.count;
    -  }
    -
    -  async handleClick() {
    -    this.count = await this.increment();
    -  }
    -
    -  // ...
    -
    -}
    -
    -export default Component;
    -
    -

    Server behavior

    Server functions will be used as local functions, simply aliasing the instance call to the class and merging the arguments with the server context

    -

    Date Convenience

    Dates are serialized as UTC in JSON and deserialized back to date objects.

    -
    import Nullstack from 'nullstack';
    -
    -class Component extends Nullstack {
    -
    -  async initiate() {
    -    const date = new Date();
    -    const verified = this.verifyDay({date});
    -  }
    -
    -  static async verifyDay({date}) {
    -    return date.getDay() === new Date().getDay();
    -  }
    -
    -  // ...
    -
    -}
    -
    -export default Component;
    -
    -

    Fetch Convenience

    Fetch is available in both server and client functions for the sake of isomorphy.

    -
    import Nullstack from 'nullstack';
    -
    -class Component extends Nullstack {
    -
    -  // ...
    -
    -  async initiate() {
    -    const url = 'https://api.github.com/repos/nullstack/nullstack/issues';
    -    const response = await fetch(url);
    -    this.issues = await response.json();
    -  }
    -
    -  // ...
    -
    -}
    -
    -export default Component;
    -
    -

    Server only imports

    Imported dependencies that are only used inside server functions will be excluded from the client bundle.

    -

    This is useful for both accessing node.js exclusive modules and reducing the client bundle size by preprocessing data like markdown without having to expose the dependency to the end-user.

    -
    import Nullstack from 'nullstack';
    -import {readFileSync} from 'fs';
    -import {Remarkable} from 'remarkable';
    -
    -class Application extends Nullstack {
    -
    -  static async getTasks() {
    -    const readme = readFileSync('README.md', 'utf-8');
    -    return new Remarkable().render(readme);
    -  }
    -
    -  // ...
    -
    -}
    -
    -export default Application;
    -
    -

    Security

    Keep in mind that every server function is similar to an express route in API and must be coded without depending on view logic for security.

    -
    -

    🔒 Server functions with the name starting with "start" (and optionally followed by an uppercase letter) do not generate an API endpoint to avoid malicious context flooding.

    -
    -
    import Nullstack from 'nullstack';
    -
    -class Component extends Nullstack {
    -
    -  static async getCount({request, count}) {
    -    if(!request.session.user) return 0;
    -    return count;
    -  }
    -
    -  // ...
    -
    -}
    -
    -export default Component;
    -
    -
    -

    💡 Server functions are not exposed to the client.

    -
    -
    -

    ✨ Learn more about the NJS file extension.

    -
    -

    Reserved words

    Server function names cannot collide with instance method names from the current class or its parent classes.

    -

    The following words cannot be used in server functions:

    -
      -
    • prepare
    • -
    • initiate
    • -
    • hydrate
    • -
    • update
    • -
    • terminate
    • -
    -

    Server functions named start will not generate an API endpoint and can only be called by other server functions.

    -

    Caveats

    Automatically generated API endpoints are not meant to be used by 3rd-party apps.

    -

    The URL and implementation may change between versions of Nullstack.

    -
    -

    ✨ If you want to build an API, learn more about how to create an API with Nullstack.

    -
    -

    Next step

    ⚔ Learn about the context.

    -
    - - - \ No newline at end of file diff --git a/docs/server-functions/index.json b/docs/server-functions/index.json deleted file mode 100644 index 8b892b64..00000000 --- a/docs/server-functions/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Server Functions","html":"

    Server functions are specialized microservices that at transpile time are converted into API entry points.

    \n

    To flag a function as a server function, you must declare it as static async.

    \n

    Being a static function means it has no access to the instance scope.

    \n

    However, instead of calling the static version from the class, you must invoke it as an instance function.

    \n

    Server functions can be called anytime in your code and are not limited to prerender steps.

    \n
    import Nullstack from 'nullstack';\n\nclass Component extends Nullstack {\n\n  static async increment(context) {\n    context.count++;\n  }\n\n  async handleClick() {\n    await this.increment();\n  }\n\n  // ...\n\n}\n\nexport default Component;\n
    \n
    \n

    ✨ Learn more about the server context.

    \n
    \n

    Client behavior

    When you call a server function from the client, the arguments will be serialized as JSON.

    \n

    The arguments will be posted against the automatically generated API and merged with the server context when it reaches the server.

    \n

    The return value of the server function will be serialized back to the client and can be seamlessly used as if it were a local function.

    \n
    import Nullstack from 'nullstack';\n\nclass Component extends Nullstack {\n\n  static async increment(context) {\n    context.count++;\n    return context.count;\n  }\n\n  async handleClick() {\n    this.count = await this.increment();\n  }\n\n  // ...\n\n}\n\nexport default Component;\n
    \n

    Server behavior

    Server functions will be used as local functions, simply aliasing the instance call to the class and merging the arguments with the server context

    \n

    Date Convenience

    Dates are serialized as UTC in JSON and deserialized back to date objects.

    \n
    import Nullstack from 'nullstack';\n\nclass Component extends Nullstack {\n\n  async initiate() {\n    const date = new Date();\n    const verified = this.verifyDay({date});\n  }\n\n  static async verifyDay({date}) {\n    return date.getDay() === new Date().getDay();\n  }\n\n  // ...\n\n}\n\nexport default Component;\n
    \n

    Fetch Convenience

    Fetch is available in both server and client functions for the sake of isomorphy.

    \n
    import Nullstack from 'nullstack';\n\nclass Component extends Nullstack {\n\n  // ...\n\n  async initiate() {\n    const url = 'https://api.github.com/repos/nullstack/nullstack/issues';\n    const response = await fetch(url);\n    this.issues = await response.json();\n  }\n\n  // ...\n\n}\n\nexport default Component;\n
    \n

    Server only imports

    Imported dependencies that are only used inside server functions will be excluded from the client bundle.

    \n

    This is useful for both accessing node.js exclusive modules and reducing the client bundle size by preprocessing data like markdown without having to expose the dependency to the end-user.

    \n
    import Nullstack from 'nullstack';\nimport {readFileSync} from 'fs';\nimport {Remarkable} from 'remarkable';\n\nclass Application extends Nullstack {\n\n  static async getTasks() {\n    const readme = readFileSync('README.md', 'utf-8');\n    return new Remarkable().render(readme);\n  }\n\n  // ...\n\n}\n\nexport default Application;\n
    \n

    Security

    Keep in mind that every server function is similar to an express route in API and must be coded without depending on view logic for security.

    \n
    \n

    🔒 Server functions with the name starting with "start" (and optionally followed by an uppercase letter) do not generate an API endpoint to avoid malicious context flooding.

    \n
    \n
    import Nullstack from 'nullstack';\n\nclass Component extends Nullstack {\n\n  static async getCount({request, count}) {\n    if(!request.session.user) return 0;\n    return count;\n  }\n\n  // ...\n\n}\n\nexport default Component;\n
    \n
    \n

    💡 Server functions are not exposed to the client.

    \n
    \n
    \n

    ✨ Learn more about the NJS file extension.

    \n
    \n

    Reserved words

    Server function names cannot collide with instance method names from the current class or its parent classes.

    \n

    The following words cannot be used in server functions:

    \n
      \n
    • prepare
    • \n
    • initiate
    • \n
    • hydrate
    • \n
    • update
    • \n
    • terminate
    • \n
    \n

    Server functions named start will not generate an API endpoint and can only be called by other server functions.

    \n

    Caveats

    Automatically generated API endpoints are not meant to be used by 3rd-party apps.

    \n

    The URL and implementation may change between versions of Nullstack.

    \n
    \n

    ✨ If you want to build an API, learn more about how to create an API with Nullstack.

    \n
    \n

    Next step

    ⚔ Learn about the context.

    \n","description":"Server functions are specialized microservices that at transpile time are converted into API entry points"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Server Functions - Nullstack","description":"Server functions are specialized microservices that at transpile time are converted into API entry points"}} \ No newline at end of file diff --git a/docs/server-request-and-response/index.html b/docs/server-request-and-response/index.html deleted file mode 100644 index 2af50aad..00000000 --- a/docs/server-request-and-response/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - Server request and response - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Server request and response

    The server key

    The server key is a proxy around the Express instance that runs Nullstack under the hood.

    -

    The server object is present only in the server context.

    -

    The following functions are tunneled back to the express server:

    -
      -
    • get
    • -
    • post
    • -
    • put
    • -
    • patch
    • -
    • delete
    • -
    • options
    • -
    • head
    • -
    • use
    • -
    -
    -

    ✨ If you wanna know how to make an API with Nullstack, this is the way.

    -
    -
    import Nullstack from 'nullstack';
    -
    -class Application extends Nullstack {
    -
    -  static async start({server}) {
    -    server.get('/api/books', (request, response) => {
    -      response.json({books: []});
    -    });
    -  }
    -
    -  // ...
    -
    -}
    -
    -export default Application;
    -
    -

    Other available keys are:

    -
      -
    • port: integer
    • -
    • maximumPayloadSize: string
    • -
    • cors: object
    • -
    -
    import Nullstack from 'nullstack';
    -
    -class Application extends Nullstack {
    -
    -  static async start({server}) {
    -    server.port = 3000;
    -    server.maximumPayloadSize = '5mb';
    -    server.cors = {
    -      origin: 'http://localhost:6969',
    -      optionsSuccessStatus: 200
    -    }
    -  }
    -
    -  // ...
    -
    -}
    -
    -export default Application;
    -
    -

    The cors object will be passed as the argument to express cors plugin

    -

    Request and Response

    Every server function context is merged with the original request and response objects from express.

    -

    If you raise a response manually it will override the framework's server-side rendering response.

    -
    import Nullstack from 'nullstack';
    -
    -class Application extends Nullstack {
    -
    -  static async getBooks({request, response}) {
    -    if(!request.session.user) {
    -      response.status(401).json({unauthorized: true});
    -    }
    -  }
    -
    -  // ...
    -
    -}
    -
    -export default Application;
    -
    -

    Next step

    ⚔ Learn about styles.

    -
    - - - \ No newline at end of file diff --git a/docs/server-request-and-response/index.json b/docs/server-request-and-response/index.json deleted file mode 100644 index 4044b3ab..00000000 --- a/docs/server-request-and-response/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Server request and response","html":"

    The server key

    The server key is a proxy around the Express instance that runs Nullstack under the hood.

    \n

    The server object is present only in the server context.

    \n

    The following functions are tunneled back to the express server:

    \n
      \n
    • get
    • \n
    • post
    • \n
    • put
    • \n
    • patch
    • \n
    • delete
    • \n
    • options
    • \n
    • head
    • \n
    • use
    • \n
    \n
    \n

    ✨ If you wanna know how to make an API with Nullstack, this is the way.

    \n
    \n
    import Nullstack from 'nullstack';\n\nclass Application extends Nullstack {\n\n  static async start({server}) {\n    server.get('/api/books', (request, response) => {\n      response.json({books: []});\n    });\n  }\n\n  // ...\n\n}\n\nexport default Application;\n
    \n

    Other available keys are:

    \n
      \n
    • port: integer
    • \n
    • maximumPayloadSize: string
    • \n
    • cors: object
    • \n
    \n
    import Nullstack from 'nullstack';\n\nclass Application extends Nullstack {\n\n  static async start({server}) {\n    server.port = 3000;\n    server.maximumPayloadSize = '5mb';\n    server.cors = {\n      origin: 'http://localhost:6969',\n      optionsSuccessStatus: 200\n    }\n  }\n\n  // ...\n\n}\n\nexport default Application;\n
    \n

    The cors object will be passed as the argument to express cors plugin

    \n

    Request and Response

    Every server function context is merged with the original request and response objects from express.

    \n

    If you raise a response manually it will override the framework's server-side rendering response.

    \n
    import Nullstack from 'nullstack';\n\nclass Application extends Nullstack {\n\n  static async getBooks({request, response}) {\n    if(!request.session.user) {\n      response.status(401).json({unauthorized: true});\n    }\n  }\n\n  // ...\n\n}\n\nexport default Application;\n
    \n

    Next step

    ⚔ Learn about styles.

    \n","description":"The server key is a proxy around the express instance that runs Nullstack under the hood"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Server request and response - Nullstack","description":"The server key is a proxy around the express instance that runs Nullstack under the hood"}} \ No newline at end of file diff --git a/docs/server-side-rendering/index.html b/docs/server-side-rendering/index.html deleted file mode 100644 index acea9f19..00000000 --- a/docs/server-side-rendering/index.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - Server-Side Rendering - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Server-Side Rendering

    Nullstack optimizes SEO and response times out of the box by generating HTML for the route that you enter the application from.

    -

    Server-side rendering is good for SEO since it gives as fast as possible crawlable markup for search engines.

    -

    Nullstack starts the application for the user by first serving HTML of only the requested page with no overhead.

    -

    Before serving the HTML, Nullstack will wait for prepare and initiate of all components of that route to be resolved.

    -

    While server-side rendering all server functions run locally without the need to fetch an API, making the process even faster.

    -

    After the document is already painted in the browser, Nullstack loads the javascript client bundle and starts the hydration process.

    -

    No further requests to the server are made to recover the application state during hydration.

    -

    The page head will generate the necessary meta tags for SEO based on the contents of the project and page context keys.

    -

    Next step

    ⚔ Learn about static site generation.

    -
    - - - \ No newline at end of file diff --git a/docs/server-side-rendering/index.json b/docs/server-side-rendering/index.json deleted file mode 100644 index b54c621f..00000000 --- a/docs/server-side-rendering/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Server-Side Rendering","html":"

    Nullstack optimizes SEO and response times out of the box by generating HTML for the route that you enter the application from.

    \n

    Server-side rendering is good for SEO since it gives as fast as possible crawlable markup for search engines.

    \n

    Nullstack starts the application for the user by first serving HTML of only the requested page with no overhead.

    \n

    Before serving the HTML, Nullstack will wait for prepare and initiate of all components of that route to be resolved.

    \n

    While server-side rendering all server functions run locally without the need to fetch an API, making the process even faster.

    \n

    After the document is already painted in the browser, Nullstack loads the javascript client bundle and starts the hydration process.

    \n

    No further requests to the server are made to recover the application state during hydration.

    \n

    The page head will generate the necessary meta tags for SEO based on the contents of the project and page context keys.

    \n

    Next step

    ⚔ Learn about static site generation.

    \n","description":"Nullstack optimizes SEO and response times out of the box by generating HTML for the route that you enter the application from"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Server-Side Rendering - Nullstack","description":"Nullstack optimizes SEO and response times out of the box by generating HTML for the route that you enter the application from"}} \ No newline at end of file diff --git a/docs/service-worker-f0cee0769c64977a287dddeaae84c7f6.js b/docs/service-worker-f0cee0769c64977a287dddeaae84c7f6.js deleted file mode 100644 index 3edf57d6..00000000 --- a/docs/service-worker-f0cee0769c64977a287dddeaae84c7f6.js +++ /dev/null @@ -1,235 +0,0 @@ -self.context = { - "environment": { - "client": false, - "server": true, - "development": false, - "production": true, - "static": true, - "key": "f0cee0769c64977a287dddeaae84c7f6" - }, - "project": { - "type": "website", - "display": "standalone", - "orientation": "portrait", - "scope": "/", - "root": "/", - "favicon": "/favicon-96x96.png", - "icons": { - "72": "/icon-72x72.png", - "96": "/icon-96x96.png", - "128": "/icon-128x128.png", - "144": "/icon-144x144.png", - "152": "/icon-152x152.png", - "180": "/icon-180x180.png", - "192": "/icon-192x192.png", - "384": "/icon-384x384.png", - "512": "/icon-512x512.png" - }, - "disallow": [], - "sitemap": true, - "cdn": "", - "protocol": "https", - "name": "Nullstack", - "domain": "nullstack.app", - "color": "#d22365", - "backgroundColor": "#2d3748" - }, - "settings": {}, - "worker": { - "enabled": true, - "fetching": false, - "preload": [ - "/nullstack.svg", - "/about", - "/application-startup", - "/context-data", - "/context-environment", - "/context-page", - "/context-project", - "/context-secrets", - "/context-settings", - "/context", - "/full-stack-lifecycle", - "/getting-started", - "/how-to-deploy-a-nullstack-application", - "/how-to-use-facebook-pixel-with-nullstack", - "/how-to-use-google-analytics-with-nullstack", - "/how-to-use-mongodb-with-nullstack", - "/instance-key", - "/instance-self", - "/njs-file-extension", - "/renderable-components", - "/routes-and-params", - "/server-functions", - "/server-request-and-response", - "/server-side-rendering", - "/service-worker", - "/stateful-components", - "/static-site-generation", - "/styles", - "/two-way-bindings", - "/documentation", - "/components", - "/about", - "/contributors", - "/roboto-v20-latin-300.woff2", - "/roboto-v20-latin-500.woff2", - "/crete-round-v9-latin-regular.woff2", - "/nullachan.png" - ], - "headers": {}, - "loading": {} - } -}; - -async function load(event) { - const response = await event.preloadResponse; - if (response) return response; - return await fetch(event.request); -} - -function toAPI(url) { - let [path, query] = url.split('?'); - if(path.indexOf('.') === -1) { - path += '/index.json'; - } - return query ? `${path}?${query}` : path; -} - -async function extractData(response) { - const html = await response.clone().text(); - const instancesLookup = 'window.instances = '; - const instances = html.split("\n").find((line) => line.indexOf(instancesLookup) > -1).split(instancesLookup)[1].slice(0, -1); - const pageLookup = 'window.page = '; - const page = html.split("\n").find((line) => line.indexOf(pageLookup) > -1).split(pageLookup)[1].slice(0, -1); - const json = `{"instances": ${instances}, "page": ${page}}`; - return new Response(json, { - headers: {'Content-Type': 'application/json'} - }); -} - -async function injectData(templateResponse, cachedDataResponse) { - const data = await cachedDataResponse.json(); - const input = await templateResponse.text(); - const output = input.split(`\n`).map((line) => { - if(line.indexOf('') > -1) { - return line.replace(/(<title\b[^>]*>)[^<>]*(<\/title>)/i, `$1${data.page.title}$2`); - } else if(line.indexOf('window.instances = ') > -1) { - return `window.instances = ${JSON.stringify(data.instances)};` - } else if(line.indexOf('window.page = ') > -1) { - return `window.page = ${JSON.stringify(data.page)};` - } else if(line.indexOf('window.worker = ') > -1) { - return line.replace('"online":false', '"online":true').replace('"responsive":false', '"responsive":true'); - } - return line; - }).join("\n"); - return new Response(output, { - headers: {'Content-Type': 'text/html'} - }); -} - -async function cacheFirst(event) { - const cache = await caches.open(self.context.environment.key); - const cachedResponse = await cache.match(event.request); - if(cachedResponse) return cachedResponse; - const response = await load(event); - await cache.put(event.request, response.clone()); - return response; -} - -async function staleWhileRevalidate(event) { - const cache = await caches.open(self.context.environment.key); - const cachedResponse = await cache.match(event.request); - const networkResponsePromise = load(event); - event.waitUntil(async function() { - const networkResponse = await networkResponsePromise; - await cache.put(event.request, networkResponse.clone()); - }()); - return cachedResponse || networkResponsePromise; -} - -async function networkFirst(event) { - const cache = await caches.open(self.context.environment.key); - try { - const networkResponse = await load(event); - await cache.put(event.request, networkResponse.clone()); - return networkResponse; - } catch(error) { - return await cache.match(event.request); - } -} - -async function networkDataFirst(event) { - const cache = await caches.open(self.context.environment.key); - const url = new URL(event.request.url); - const api = url.pathname + '/index.json'; - try { - const response = await load(event); - const dataResponse = await extractData(response); - await cache.put(api, dataResponse); - return response; - } catch(error) { - const fallbackResponse = await cache.match(`/offline-${self.context.environment.key}/index.html`); - const cachedDataResponse = await cache.match(api); - if(cachedDataResponse) { - return await injectData(fallbackResponse, cachedDataResponse); - } else { - return fallbackResponse; - } - } -} - -function install(event) { - const urls = [ - '/', - ...self.context.worker.preload.map(toAPI), - '/offline-' + self.context.environment.key + '/index.html', - '/client-' + self.context.environment.key + '.css', - '/client-' + self.context.environment.key + '.js', - '/manifest-' + self.context.environment.key + '.json' - ]; - event.waitUntil(async function() { - const cache = await caches.open(self.context.environment.key); - await cache.addAll([...new Set(urls)]); - const homeResponse = await cache.match('/'); - const homeDataResponse = await extractData(homeResponse); - await cache.put('/index.json', homeDataResponse); - self.skipWaiting(); - }()); -} - -self.addEventListener('install', install); - -function activate(event) { - event.waitUntil(async function() { - const cacheNames = await caches.keys(); - const cachesToDelete = cacheNames.filter(cacheName => cacheName !== self.context.environment.key); - await Promise.all(cachesToDelete.map((cacheName) => caches.delete(cacheName))); - if (self.registration.navigationPreload) { - await self.registration.navigationPreload.enable(); - } - self.clients.claim(); - }()); -} - -self.addEventListener('activate', activate); - -function staticStrategy(event) { - event.waitUntil(async function() { - const url = new URL(event.request.url); - if(url.origin !== location.origin) return; - if(event.request.method !== 'GET') return; - if(url.pathname.indexOf(self.context.environment.key) > -1) { - return event.respondWith(cacheFirst(event)); - } - if(url.pathname.indexOf('.') > -1) { - return event.respondWith(staleWhileRevalidate(event)); - } - if(url.pathname === '/') { - return event.respondWith(networkFirst(event)); - } - event.respondWith(networkDataFirst(event)); - }()); -} - -self.addEventListener('fetch', staticStrategy); \ No newline at end of file diff --git a/docs/service-worker/index.html b/docs/service-worker/index.html deleted file mode 100644 index a24b5997..00000000 --- a/docs/service-worker/index.html +++ /dev/null @@ -1,289 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta name="generator" content="Created with Nullstack - https://nullstack.app" /> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <title>Context Service Worker - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Context Service Worker

    The worker is a proxy in the framework store part of your context and gives you granular control of your PWA behavior.

    -

    This key is readwrite in the server context.

    -

    This key is readonly in the client context.

    -

    Worker keys will be used to generate the service worker file and should be set during the application startup.

    -

    Worker keys are frozen after the application startup.

    -

    The following keys are available in the object during the startup:

    -
      -
    • enabled: boolean
    • -
    • preload: string array (relative paths)
    • -
    • headers: object
    • -
    -

    The enabled key defines if the service worker will be automatically registered by Nullstack.

    -

    By default enabled is set to true on production mode and false on development mode.

    -

    Preload is an array of paths that will be cached when the service worker is installed.

    -

    The assets required to start the application will be preloaded automatically, and you should configure only the extra pages you want to have available offline.

    -
    import Nullstack from 'nullstack';
    -import path from 'path';
    -import {readdirSync} from 'fs';
    -
    -class Application extends Nullstack {
    -
    -  static async start({worker}) {
    -    const articles = readdirSync(path.join(__dirname, '..', 'articles'));
    -    worker.preload = [
    -      ...articles.map((article) => '/' + article.replace('.md', '')),
    -      '/nullstack.svg',
    -      '/documentation',
    -      '/components'
    -    ]
    -  }
    -  
    -  // ...
    -
    -}
    -
    -export default Application;
    -
    -
    -

    💡 the example above is extracted from this repository and allows the documentation to be fully accessible offline.

    -
    -

    The following keys are available as readonly in the client context:

    -
      -
    • enabled: boolean
    • -
    • preload: string array (relative paths)
    • -
    • online: boolean
    • -
    • fetching: boolean
    • -
    • responsive: boolean
    • -
    • installation: BeforeInstallPromptEvent
    • -
    • registration: ServiceWorkerRegistration
    • -
    • loading: object
    • -
    -

    The following keys are available as readwrite in the client context:

    -
      -
    • headers: object
    • -
    -

    The responsive key determines if the application has all the responses it needs to render the current page.

    -

    Nullstack will try to keep your application responsive as long as possible and set the key to false only when there are no ways of retrieving any response from the network or offline according to the fetch strategy for the environment.

    -

    The online key will listen for network events and rerender the application when navigator.onLine value changes.

    -

    When the application is back online Nullstack will try to make the application responsive again and rerender if necessary.

    -
    import Nullstack from 'nullstack';
    -// ...
    -
    -class Application extends Nullstack {
    - 
    -  // ...
    -
    -  render({worker}) {
    -    if(!worker.responsive) {
    -      return <OfflineWarning />
    -    }
    -    return (
    -      <main>
    -        <Home route="/" />
    -      </main>
    -    )
    -  }
    -
    -}
    -
    -

    You can access the current service worker registration and installation from the worker key to control the flow of your PWA.

    -

    The registration key refers to the service worker registration and will be only available once the registration process is complete.

    -

    The installation key refers to the deferred installation prompt event and will only be available if the beforeinstallprompt event is triggered.

    -
    import Nullstack from 'nullstack';
    -
    -class PWAInstaller extends Nullstack {
    -
    -  installed = false;
    -  hidden = false;
    -
    -  async prompt({worker}) {
    -    try {
    -      worker.installation.prompt();
    -      const {outcome} = await worker.installation.userChoice;
    -      if (outcome === 'accepted') {
    -        console.log('User accepted the A2HS prompt');
    -      } else {
    -        console.log('User dismissed the A2HS prompt');
    -      }
    -    } finally {
    -      this.hidden = true;
    -    }
    -  }
    -  
    -  render({worker, project}) {
    -    if(this.hidden) return false;
    -    if(!worker.installation) return false;
    -    return (
    -      <div>
    -        <img src={project.favicon} />
    -        <p> Do you want to add {project.name} to your home screen?</p>
    -        <button onclick={this.prompt}> Install </button>
    -        <button onclick={{hidden: true}}> Not Now </button>
    -      </div>
    -    )
    -  }
    -
    -}
    -
    -export default PWAInstaller;
    -
    -

    Loading Screens

    When a server function is called fetching will be set to true until the response is resolved.

    -

    When a server function is called a key with the name of the server function invoked will be set to true in the loading key until the response is resolved.

    -

    Any key you invoke on the loading object will always return a boolean instead of undefined for consistency.

    -

    When the server is emulating the client context for server-side rendering, every key of the loading object will always return false, saving multiple render cycles in performance.

    -
    import Nullstack from 'nullstack';
    -
    -class Page extends Nullstack {
    -
    -  static async save() {
    -    // ...
    -  }
    -
    -  async submit() {
    -    await this.save();
    -  }
    - 
    -  render({worker}) {
    -    return (
    -      <form onsubmit={this.save}> 
    -        {worker.fetching && 
    -          <span> loading... </span>
    -        }
    -        <button disabled={worker.loading.save}> 
    -          Save
    -        </button>
    -      </form>
    -    )
    -  }
    -
    -}
    -
    -export default Page;
    -
    -

    Custom headers

    You can use the headers key to configure the headers that the worker will use when fetching a server function.

    -
    -

    🔥 Headers will be ignored when a server function is called during the server-side rendering process.

    -
    -
    import Nullstack from 'nullstack';
    -
    -class LoginPage extends Nullstack {
    -
    -  // ...
    -
    -  async submit({worker}) {
    -    // ...
    -    this.headers['Authorization'] = `Bearer ${token}`;
    -    // ...
    -  }
    -
    -  static async authorize({request}) {
    -    const authorization = request.headers['Authorization'];
    -    // ...
    -  }
    -  
    -  // ...
    -
    -}
    -
    -
    -export default LoginPage;
    -
    -
    -

    ✨ Learn more about the server request and response

    -
    -

    Server-side render strategy

      -
    • Requests for different origins will be fetched normally;
    • -
    • Requests that are not GET will be fetched normally;
    • -
    • Fingerprinted assets will be loaded into cache at installation time;
    • -
    • Fingerprinted assets will be loaded from cache first then fallback to the network if needed;
    • -
    • Paths with an extension will be retrieved stale and update the cache in the background for subsequent request;
    • -
    • Navigation paths will be loaded from the network then fallback to a page in which worker.responsive and worker.online are set to false;
    • -
    -

    Static-site generation strategy

      -
    • Requests for different origins will be fetched normally;
    • -
    • Requests that are not GET will be fetched normally;
    • -
    • Fingerprinted assets will be loaded into cache at installation time;
    • -
    • Fingerprinted assets will be loaded from cache first then fallback to the network if needed;
    • -
    • Paths with an extension will be retrieved stale and update the cache in the background for subsequent request;
    • -
    • The home page will be loaded network first and then fallback to a cached copy if needed;
    • -
    • Navigation paths will instead load only the static API data and merge it with the application template to generate a response.
    • -
    • Navigating to a static route will cache only the data of that page;
    • -
    • When data is not available in the cache or network it will fallback to a page in which worker.responsive and worker.online are set to false;
    • -
    -

    Custom Strategy

    Nullstack will install automatically your service worker if enabled is set to true with the following events:

    -
      -
    • install
    • -
    • activate
    • -
    • fetch
    • -
    -

    You can override any of those events by creating a service-worker.js in the public folder;

    -

    If any of the keywords above are found Nullstack will inject your function in the service worker code instead of the default.

    -

    For convenience a context key is injected in the service worker self with the following keys:

    - -
    function activate(event) {
    -  event.waitUntil(async function() {
    -    const cacheNames = await caches.keys();
    -    const cachesToDelete = cacheNames.filter(cacheName => cacheName !== self.context.environment.key);
    -    await Promise.all(cachesToDelete.map((cacheName) => caches.delete(cacheName)));
    -    if (self.registration.navigationPreload) {
    -      await self.registration.navigationPreload.enable();
    -    }
    -    self.clients.claim();
    -  }());
    -}
    -
    -self.addEventListener('activate', activate);
    -
    -
    -

    💡 The example above is extracted from the generated service worker and uses self.context.environment.key

    -
    -

    Next step

    ⚔ Learn how to deploy a Nullstack application.

    -
    - - - \ No newline at end of file diff --git a/docs/service-worker/index.json b/docs/service-worker/index.json deleted file mode 100644 index 4f9258f8..00000000 --- a/docs/service-worker/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Context Service Worker","html":"

    The worker is a proxy in the framework store part of your context and gives you granular control of your PWA behavior.

    \n

    This key is readwrite in the server context.

    \n

    This key is readonly in the client context.

    \n

    Worker keys will be used to generate the service worker file and should be set during the application startup.

    \n

    Worker keys are frozen after the application startup.

    \n

    The following keys are available in the object during the startup:

    \n
      \n
    • enabled: boolean
    • \n
    • preload: string array (relative paths)
    • \n
    • headers: object
    • \n
    \n

    The enabled key defines if the service worker will be automatically registered by Nullstack.

    \n

    By default enabled is set to true on production mode and false on development mode.

    \n

    Preload is an array of paths that will be cached when the service worker is installed.

    \n

    The assets required to start the application will be preloaded automatically, and you should configure only the extra pages you want to have available offline.

    \n
    import Nullstack from 'nullstack';\nimport path from 'path';\nimport {readdirSync} from 'fs';\n\nclass Application extends Nullstack {\n\n  static async start({worker}) {\n    const articles = readdirSync(path.join(__dirname, '..', 'articles'));\n    worker.preload = [\n      ...articles.map((article) => '/' + article.replace('.md', '')),\n      '/nullstack.svg',\n      '/documentation',\n      '/components'\n    ]\n  }\n  \n  // ...\n\n}\n\nexport default Application;\n
    \n
    \n

    💡 the example above is extracted from this repository and allows the documentation to be fully accessible offline.

    \n
    \n

    The following keys are available as readonly in the client context:

    \n
      \n
    • enabled: boolean
    • \n
    • preload: string array (relative paths)
    • \n
    • online: boolean
    • \n
    • fetching: boolean
    • \n
    • responsive: boolean
    • \n
    • installation: BeforeInstallPromptEvent
    • \n
    • registration: ServiceWorkerRegistration
    • \n
    • loading: object
    • \n
    \n

    The following keys are available as readwrite in the client context:

    \n
      \n
    • headers: object
    • \n
    \n

    The responsive key determines if the application has all the responses it needs to render the current page.

    \n

    Nullstack will try to keep your application responsive as long as possible and set the key to false only when there are no ways of retrieving any response from the network or offline according to the fetch strategy for the environment.

    \n

    The online key will listen for network events and rerender the application when navigator.onLine value changes.

    \n

    When the application is back online Nullstack will try to make the application responsive again and rerender if necessary.

    \n
    import Nullstack from 'nullstack';\n// ...\n\nclass Application extends Nullstack {\n \n  // ...\n\n  render({worker}) {\n    if(!worker.responsive) {\n      return <OfflineWarning />\n    }\n    return (\n      <main>\n        <Home route=\"/\" />\n      </main>\n    )\n  }\n\n}\n
    \n

    You can access the current service worker registration and installation from the worker key to control the flow of your PWA.

    \n

    The registration key refers to the service worker registration and will be only available once the registration process is complete.

    \n

    The installation key refers to the deferred installation prompt event and will only be available if the beforeinstallprompt event is triggered.

    \n
    import Nullstack from 'nullstack';\n\nclass PWAInstaller extends Nullstack {\n\n  installed = false;\n  hidden = false;\n\n  async prompt({worker}) {\n    try {\n      worker.installation.prompt();\n      const {outcome} = await worker.installation.userChoice;\n      if (outcome === 'accepted') {\n        console.log('User accepted the A2HS prompt');\n      } else {\n        console.log('User dismissed the A2HS prompt');\n      }\n    } finally {\n      this.hidden = true;\n    }\n  }\n  \n  render({worker, project}) {\n    if(this.hidden) return false;\n    if(!worker.installation) return false;\n    return (\n      <div>\n        <img src={project.favicon} />\n        <p> Do you want to add {project.name} to your home screen?</p>\n        <button onclick={this.prompt}> Install </button>\n        <button onclick={{hidden: true}}> Not Now </button>\n      </div>\n    )\n  }\n\n}\n\nexport default PWAInstaller;\n
    \n

    Loading Screens

    When a server function is called fetching will be set to true until the response is resolved.

    \n

    When a server function is called a key with the name of the server function invoked will be set to true in the loading key until the response is resolved.

    \n

    Any key you invoke on the loading object will always return a boolean instead of undefined for consistency.

    \n

    When the server is emulating the client context for server-side rendering, every key of the loading object will always return false, saving multiple render cycles in performance.

    \n
    import Nullstack from 'nullstack';\n\nclass Page extends Nullstack {\n\n  static async save() {\n    // ...\n  }\n\n  async submit() {\n    await this.save();\n  }\n \n  render({worker}) {\n    return (\n      <form onsubmit={this.save}> \n        {worker.fetching && \n          <span> loading... </span>\n        }\n        <button disabled={worker.loading.save}> \n          Save\n        </button>\n      </form>\n    )\n  }\n\n}\n\nexport default Page;\n
    \n

    Custom headers

    You can use the headers key to configure the headers that the worker will use when fetching a server function.

    \n
    \n

    🔥 Headers will be ignored when a server function is called during the server-side rendering process.

    \n
    \n
    import Nullstack from 'nullstack';\n\nclass LoginPage extends Nullstack {\n\n  // ...\n\n  async submit({worker}) {\n    // ...\n    this.headers['Authorization'] = `Bearer ${token}`;\n    // ...\n  }\n\n  static async authorize({request}) {\n    const authorization = request.headers['Authorization'];\n    // ...\n  }\n  \n  // ...\n\n}\n\n\nexport default LoginPage;\n
    \n
    \n

    ✨ Learn more about the server request and response

    \n
    \n

    Server-side render strategy

      \n
    • Requests for different origins will be fetched normally;
    • \n
    • Requests that are not GET will be fetched normally;
    • \n
    • Fingerprinted assets will be loaded into cache at installation time;
    • \n
    • Fingerprinted assets will be loaded from cache first then fallback to the network if needed;
    • \n
    • Paths with an extension will be retrieved stale and update the cache in the background for subsequent request;
    • \n
    • Navigation paths will be loaded from the network then fallback to a page in which worker.responsive and worker.online are set to false;
    • \n
    \n

    Static-site generation strategy

      \n
    • Requests for different origins will be fetched normally;
    • \n
    • Requests that are not GET will be fetched normally;
    • \n
    • Fingerprinted assets will be loaded into cache at installation time;
    • \n
    • Fingerprinted assets will be loaded from cache first then fallback to the network if needed;
    • \n
    • Paths with an extension will be retrieved stale and update the cache in the background for subsequent request;
    • \n
    • The home page will be loaded network first and then fallback to a cached copy if needed;
    • \n
    • Navigation paths will instead load only the static API data and merge it with the application template to generate a response.
    • \n
    • Navigating to a static route will cache only the data of that page;
    • \n
    • When data is not available in the cache or network it will fallback to a page in which worker.responsive and worker.online are set to false;
    • \n
    \n

    Custom Strategy

    Nullstack will install automatically your service worker if enabled is set to true with the following events:

    \n
      \n
    • install
    • \n
    • activate
    • \n
    • fetch
    • \n
    \n

    You can override any of those events by creating a service-worker.js in the public folder;

    \n

    If any of the keywords above are found Nullstack will inject your function in the service worker code instead of the default.

    \n

    For convenience a context key is injected in the service worker self with the following keys:

    \n\n
    function activate(event) {\n  event.waitUntil(async function() {\n    const cacheNames = await caches.keys();\n    const cachesToDelete = cacheNames.filter(cacheName => cacheName !== self.context.environment.key);\n    await Promise.all(cachesToDelete.map((cacheName) => caches.delete(cacheName)));\n    if (self.registration.navigationPreload) {\n      await self.registration.navigationPreload.enable();\n    }\n    self.clients.claim();\n  }());\n}\n\nself.addEventListener('activate', activate);\n
    \n
    \n

    💡 The example above is extracted from the generated service worker and uses self.context.environment.key

    \n
    \n

    Next step

    ⚔ Learn how to deploy a Nullstack application.

    \n","description":"The worker is a proxy in the framework store part of your context and gives you granular control of your PWA behavior."},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Context Service Worker - Nullstack","description":"The worker is a proxy in the framework store part of your context and gives you granular control of your PWA behavior."}} \ No newline at end of file diff --git a/docs/sitemap.xml b/docs/sitemap.xml deleted file mode 100644 index c0346aa3..00000000 --- a/docs/sitemap.xml +++ /dev/null @@ -1 +0,0 @@ -https://nullstack.app/2021-01-161.0https://nullstack.app/about2021-01-16https://nullstack.app/documentation2021-01-160.8https://nullstack.app/components2021-01-160.3https://nullstack.app/contributors2021-01-16https://nullstack.app/getting-started2021-01-16https://nullstack.app/server-side-rendering2021-01-16https://nullstack.app/full-stack-lifecycle2021-01-16https://nullstack.app/static-site-generation2021-01-16https://nullstack.app/server-functions2021-01-16https://nullstack.app/context2021-01-16https://nullstack.app/routes-and-params2021-01-16https://nullstack.app/renderable-components2021-01-16https://nullstack.app/two-way-bindings2021-01-16https://nullstack.app/stateful-components2021-01-16https://nullstack.app/waifu2021-01-16https://nullstack.app/application-startup2021-01-16https://nullstack.app/context-data2021-01-16https://nullstack.app/context-environment2021-01-16https://nullstack.app/context-page2021-01-16https://nullstack.app/context-project2021-01-16https://nullstack.app/context-settings2021-01-16https://nullstack.app/context-secrets2021-01-16https://nullstack.app/instance-self2021-01-16https://nullstack.app/instance-key2021-01-16https://nullstack.app/server-request-and-response2021-01-16https://nullstack.app/styles2021-01-16https://nullstack.app/njs-file-extension2021-01-16https://nullstack.app/service-worker2021-01-16https://nullstack.app/how-to-deploy-a-nullstack-application2021-01-16https://nullstack.app/how-to-use-mongodb-with-nullstack2021-01-16https://nullstack.app/how-to-use-google-analytics-with-nullstack2021-01-16https://nullstack.app/how-to-use-facebook-pixel-with-nullstack2021-01-16https://nullstack.app/4042021-01-16 \ No newline at end of file diff --git a/docs/stateful-components/index.html b/docs/stateful-components/index.html deleted file mode 100644 index 365821fc..00000000 --- a/docs/stateful-components/index.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - Stateful Components - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Stateful Components

    A productive full-stack web framework should not force you to think about framework details.

    -

    Nullstack takes control of its subclasses and generates a proxy for each instance.

    -

    When you call anything on your class you are actually telling Nullstack what to do with the environment behind the scenes.

    -

    This allows you to use vanilla javascript operations like assigning to a variable and see the reflection in the dom.

    -

    Mutability

    You can mutate instance variables to update your application state.

    -

    Functions are automatically bound to the instance proxy and can be passed as a reference to events.

    -

    Events are declared like normal HTML attributes.

    -
    import Nullstack from 'nullstack';
    -
    -class Counter extends Nullstack {
    -
    -  count = 0;
    -
    -  increment() {
    -    this.count++;
    -  }
    -  
    -  render() {
    -    return (
    -      <button onclick={this.increment}> 
    -        {this.count}
    -      </button>
    -    )
    -  }
    -
    -}
    -
    -export default Counter;
    -
    -
    -

    💡 Updates are made in batches, usually while awaiting async calls, so making multiple assignments have no performance costs!

    -
    -

    Object Events

    You can shortcut events that are simple assignments by passing an object to the event.

    -

    Each key of the object will be assigned to the instance.

    -
    import Nullstack from 'nullstack';
    -
    -class Counter extends Nullstack {
    -
    -  count = 0;
    -  
    -  render() {
    -    return (
    -      <button onclick={{count: this.count + 1}}> 
    -        {this.count}
    -      </button>
    -    )
    -  }
    -
    -}
    -
    -export default Counter;
    -
    -

    Event Source

    By default, events refer to this when you pass an object.

    -

    You can use the source attribute to define which object will receive the assignments.

    -
    import Nullstack from 'nullstack';
    -
    -class Paginator extends Nullstack {
    -  
    -  render({params}) {
    -    return (
    -      <button source={params} onclick={{page: 1}}> 
    -        First Page
    -      </button>
    -    )
    -  }
    -
    -}
    -
    -export default Paginator;
    -
    -
    -

    ✨ Learn more about context params.

    -
    -
    -

    💡 If you do not declare a source to the event, Nullstack will inject a source={this} at transpile time in order to completely skip the runtime lookup process!

    -
    -

    Event Context

    Attributes of the event target will be merged to the instance context and can be destructured in the function signature.

    -
    import Nullstack from 'nullstack';
    -
    -class Counter extends Nullstack {
    -
    -  count = 0;
    -
    -  increment({delta}) {
    -    this.count += delta;
    -  }
    -  
    -  render() {
    -    return (
    -      <button onclick={this.increment} delta={2}> 
    -        {this.count}
    -      </button>
    -    )
    -  }
    -
    -}
    -
    -export default Counter;
    -
    -
    -

    💡 Any attribute with primitive value will be added to the DOM.

    -
    -
    -

    ✨ Consider using data attributes to make your html valid.

    -
    -

    Original Event

    The browser default behavior is prevented by default.

    -

    You can opt-out of this by declaring a default attribute to the event element.

    -

    A reference to the original event is always merged with the function context.

    -
    import Nullstack from 'nullstack';
    -
    -class Form extends Nullstack {
    -
    -  submit({event}) {
    -    event.preventDefault();
    -  }
    -  
    -  render() {
    -    return (
    -      <form onsubmit={this.submit} default>
    -        <button> Submit </button>
    -      </form>
    -    )
    -  }
    -
    -}
    -
    -export default Form;
    -
    -

    Next steps

    ⚔ Learn about the full-stack lifecycle.

    -
    - - - \ No newline at end of file diff --git a/docs/stateful-components/index.json b/docs/stateful-components/index.json deleted file mode 100644 index 139e9c26..00000000 --- a/docs/stateful-components/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Stateful Components","html":"

    A productive full-stack web framework should not force you to think about framework details.

    \n

    Nullstack takes control of its subclasses and generates a proxy for each instance.

    \n

    When you call anything on your class you are actually telling Nullstack what to do with the environment behind the scenes.

    \n

    This allows you to use vanilla javascript operations like assigning to a variable and see the reflection in the dom.

    \n

    Mutability

    You can mutate instance variables to update your application state.

    \n

    Functions are automatically bound to the instance proxy and can be passed as a reference to events.

    \n

    Events are declared like normal HTML attributes.

    \n
    import Nullstack from 'nullstack';\n\nclass Counter extends Nullstack {\n\n  count = 0;\n\n  increment() {\n    this.count++;\n  }\n  \n  render() {\n    return (\n      <button onclick={this.increment}> \n        {this.count}\n      </button>\n    )\n  }\n\n}\n\nexport default Counter;\n
    \n
    \n

    💡 Updates are made in batches, usually while awaiting async calls, so making multiple assignments have no performance costs!

    \n
    \n

    Object Events

    You can shortcut events that are simple assignments by passing an object to the event.

    \n

    Each key of the object will be assigned to the instance.

    \n
    import Nullstack from 'nullstack';\n\nclass Counter extends Nullstack {\n\n  count = 0;\n  \n  render() {\n    return (\n      <button onclick={{count: this.count + 1}}> \n        {this.count}\n      </button>\n    )\n  }\n\n}\n\nexport default Counter;\n
    \n

    Event Source

    By default, events refer to this when you pass an object.

    \n

    You can use the source attribute to define which object will receive the assignments.

    \n
    import Nullstack from 'nullstack';\n\nclass Paginator extends Nullstack {\n  \n  render({params}) {\n    return (\n      <button source={params} onclick={{page: 1}}> \n        First Page\n      </button>\n    )\n  }\n\n}\n\nexport default Paginator;\n
    \n
    \n

    ✨ Learn more about context params.

    \n
    \n
    \n

    💡 If you do not declare a source to the event, Nullstack will inject a source={this} at transpile time in order to completely skip the runtime lookup process!

    \n
    \n

    Event Context

    Attributes of the event target will be merged to the instance context and can be destructured in the function signature.

    \n
    import Nullstack from 'nullstack';\n\nclass Counter extends Nullstack {\n\n  count = 0;\n\n  increment({delta}) {\n    this.count += delta;\n  }\n  \n  render() {\n    return (\n      <button onclick={this.increment} delta={2}> \n        {this.count}\n      </button>\n    )\n  }\n\n}\n\nexport default Counter;\n
    \n
    \n

    💡 Any attribute with primitive value will be added to the DOM.

    \n
    \n
    \n

    ✨ Consider using data attributes to make your html valid.

    \n
    \n

    Original Event

    The browser default behavior is prevented by default.

    \n

    You can opt-out of this by declaring a default attribute to the event element.

    \n

    A reference to the original event is always merged with the function context.

    \n
    import Nullstack from 'nullstack';\n\nclass Form extends Nullstack {\n\n  submit({event}) {\n    event.preventDefault();\n  }\n  \n  render() {\n    return (\n      <form onsubmit={this.submit} default>\n        <button> Submit </button>\n      </form>\n    )\n  }\n\n}\n\nexport default Form;\n
    \n

    Next steps

    ⚔ Learn about the full-stack lifecycle.

    \n","description":"A productive full-stack web framework should not force you to think about framework details"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Stateful Components - Nullstack","description":"A productive full-stack web framework should not force you to think about framework details"}} \ No newline at end of file diff --git a/docs/static-site-generation/index.html b/docs/static-site-generation/index.html deleted file mode 100644 index 93557830..00000000 --- a/docs/static-site-generation/index.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - Static Site Generation - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Static Site Generation

    Use Nullstack to generate static websites for lightning-fast static applications using the full power of the Nullstack client without the need for a node.js back-end.

    -

    Static sites are useful for read-only applications like blogs and documentation.

    -
    -

    💡 This documentation is actually a static site generated with Nullstack.

    -
    -

    All the benefits of server-side rendering apply to static generated sites.

    -

    You can generate a static website from your Nullstack application with the following NPX command:

    -
    npx create-nullstatic-app
    -
    -
    -

    🔥 You must be in a Nullstack project folder to run this command.

    -
    -

    By default, it will create your Nullstatic application in the static folder.

    -

    You can change the folder by passing it as an argument to the command:

    -
    npx create-nullstatic-app docs
    -
    -

    The Nullstatic generator will run your application in production mode and crawl every link to an internal route it finds in your DOM.

    -
    -

    💡 Make sure to have the server production port free when you run this command.

    -
    -

    The manifest.json and the contents of the public folder will be copied into the target folder.

    -

    Besides generating raw HTML it will also generate a JSON file for each route with a copy of the state.

    -

    On the first visit to your static application, HTML will be served and hydrated.

    -

    On the subsequent requests, Nullstack will fetch the generated JSON and update the application state without ever reloading the page.

    -

    This, in fact, gives you not only a static generated site, but a static generated API that feeds a Single Page Application with zero costs.

    -

    Good Pratices

    You can add a script to your package.json to generate your static website in a custom folder:

    -
    {
    -  "name": "nullstack.github.io",
    -  "version": "0.0.1",
    -  "description": "",
    -  "author": "",
    -  "license": "ISC",
    -  "devDependencies": {
    -    "nullstack": "~0.9.0"
    -  },
    -  "scripts": {
    -    "start": "npx webpack --config node_modules/nullstack/webpack.config.js --mode=development --watch",
    -    "build": "npx webpack --config node_modules/nullstack/webpack.config.js --mode=production",
    -    "ssg": "npx create-nullstatic-app docs"
    -  }
    -}
    -
    -
    -

    Caveats

    Nullstatic only crawls your application up to the initiate resolution, further API requests triggered by events will be ignored.

    -

    Nullstatic will crawl a /404 URL and generate both a /404.html and a /404/index.html.

    -

    Next step

    ⚔ Learn more about the service worker.

    -
    - - - \ No newline at end of file diff --git a/docs/static-site-generation/index.json b/docs/static-site-generation/index.json deleted file mode 100644 index 23a2a56a..00000000 --- a/docs/static-site-generation/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Static Site Generation","html":"

    Use Nullstack to generate static websites for lightning-fast static applications using the full power of the Nullstack client without the need for a node.js back-end.

    \n

    Static sites are useful for read-only applications like blogs and documentation.

    \n
    \n

    💡 This documentation is actually a static site generated with Nullstack.

    \n
    \n

    All the benefits of server-side rendering apply to static generated sites.

    \n

    You can generate a static website from your Nullstack application with the following NPX command:

    \n
    npx create-nullstatic-app\n
    \n
    \n

    🔥 You must be in a Nullstack project folder to run this command.

    \n
    \n

    By default, it will create your Nullstatic application in the static folder.

    \n

    You can change the folder by passing it as an argument to the command:

    \n
    npx create-nullstatic-app docs\n
    \n

    The Nullstatic generator will run your application in production mode and crawl every link to an internal route it finds in your DOM.

    \n
    \n

    💡 Make sure to have the server production port free when you run this command.

    \n
    \n

    The manifest.json and the contents of the public folder will be copied into the target folder.

    \n

    Besides generating raw HTML it will also generate a JSON file for each route with a copy of the state.

    \n

    On the first visit to your static application, HTML will be served and hydrated.

    \n

    On the subsequent requests, Nullstack will fetch the generated JSON and update the application state without ever reloading the page.

    \n

    This, in fact, gives you not only a static generated site, but a static generated API that feeds a Single Page Application with zero costs.

    \n

    Good Pratices

    You can add a script to your package.json to generate your static website in a custom folder:

    \n
    {\n  \"name\": \"nullstack.github.io\",\n  \"version\": \"0.0.1\",\n  \"description\": \"\",\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"nullstack\": \"~0.9.0\"\n  },\n  \"scripts\": {\n    \"start\": \"npx webpack --config node_modules/nullstack/webpack.config.js --mode=development --watch\",\n    \"build\": \"npx webpack --config node_modules/nullstack/webpack.config.js --mode=production\",\n    \"ssg\": \"npx create-nullstatic-app docs\"\n  }\n}\n\n
    \n

    Caveats

    Nullstatic only crawls your application up to the initiate resolution, further API requests triggered by events will be ignored.

    \n

    Nullstatic will crawl a /404 URL and generate both a /404.html and a /404/index.html.

    \n

    Next step

    ⚔ Learn more about the service worker.

    \n","description":"Use Nullstack to generate static websites for lightning-fast static applications using the full power of Nullstack without the need for a node.js back-end"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Static Site Generation - Nullstack","description":"Use Nullstack to generate static websites for lightning-fast static applications using the full power of Nullstack without the need for a node.js back-end"}} \ No newline at end of file diff --git a/docs/styles/index.html b/docs/styles/index.html deleted file mode 100644 index f1960e76..00000000 --- a/docs/styles/index.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - Styles - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Styles

    Using styles with Nullstack is as simple as importing a style file.

    -

    Nullstack comes with a SASS loader by default, but you can still use vanilla CSS.

    -
    -

    ✨ It's a good practice to import a file with the same name as the component.

    -
    -
    import Nullstack from 'nullstack';
    -import './Header.scss';
    -
    -class Header extends Nullstack {
    -  // ...
    -}
    -
    -export default Header;
    -
    -

    In production mode Nullstack uses PurceCSS, which cleans your client.css file, but has some gotchas.

    -
    -

    ✨ Learn more about safelisting your css

    -
    -

    Next step

    ⚔ Learn about the NJS file extension.

    -
    - - - \ No newline at end of file diff --git a/docs/styles/index.json b/docs/styles/index.json deleted file mode 100644 index 1a37ff7a..00000000 --- a/docs/styles/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Styles","html":"

    Using styles with Nullstack is as simple as importing a style file.

    \n

    Nullstack comes with a SASS loader by default, but you can still use vanilla CSS.

    \n
    \n

    ✨ It's a good practice to import a file with the same name as the component.

    \n
    \n
    import Nullstack from 'nullstack';\nimport './Header.scss';\n\nclass Header extends Nullstack {\n  // ...\n}\n\nexport default Header;\n
    \n

    In production mode Nullstack uses PurceCSS, which cleans your client.css file, but has some gotchas.

    \n
    \n

    ✨ Learn more about safelisting your css

    \n
    \n

    Next step

    ⚔ Learn about the NJS file extension.

    \n","description":"Using styles with Nullstack is as simple as importing a style file"},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Styles - Nullstack","description":"Using styles with Nullstack is as simple as importing a style file"}} \ No newline at end of file diff --git a/docs/two-way-bindings/index.html b/docs/two-way-bindings/index.html deleted file mode 100644 index c9811c91..00000000 --- a/docs/two-way-bindings/index.html +++ /dev/null @@ -1,289 +0,0 @@ - - - - - - - Two-Way Binding - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Two-Way Binding

    Big chunks of code in a progressive web application is dedicated to reacting to user input.

    -

    The process of controlling user input can be broken into 3 tedious steps:

    -
      -
    • Declaring a variable with the initial value;
    • -
    • Passing the initial value to the input;
    • -
    • Observing changes in the input and assigning the new value to the variable.
    • -
    -

    The last step might include typecasting and other value treatments.

    -

    This process in which you manually do all these steps is called one-way binding, it is the default in many frameworks, and is possible in Nullstack.

    -
    import Nullstack from 'nullstack';
    -
    -class Form extends Nullstack {
    -
    -  number = 1;
    -  string = '';
    -
    -  updateString({event}) {
    -    this.string = event.target.value;
    -  }
    -
    -  updateNumber({event}) {
    -    this.number = parseInt(event.target.value);
    -  }
    - 
    -  render() {
    -    return (
    -      <form>
    -        <input
    -          type="text"
    -          name="string"
    -          value={this.string}
    -          oninput={this.updateString}
    -        />
    -        <input
    -          type="number"
    -          name="number"
    -          value={this.number}
    -          oninput={this.updateNumber}
    -        />
    -      </form>
    -    )
    -  }
    -
    -}
    -
    -export default Form;
    -
    -

    The bind attribute

    Bind reduces drastically the amount of glue code you have to type in your application.

    -

    You can shortcut setting a value, name, and event with the bind attribute.

    -
    -

    💡 Nullstack will simply replace bind with the value, name, and event under the hood.

    -
    -

    Bind will generate an event that automatically typecasts to the previous primitive type the value was.

    -

    You can pass any variable to the bind as long as its parent object is mentioned.

    -
    import Nullstack from 'nullstack';
    -
    -class Form extends Nullstack {
    -
    -  number = 1;
    -  string = '';
    - 
    -  render() {
    -    return (
    -      <form>
    -        <input type="text" bind={this.string} />
    -        <input type="number" bind={this.number} />
    -      </form>
    -    )
    -  }
    -
    -}
    -
    -export default Form;
    -
    -

    Bound Events

    The following events are set for each type of input:

    -
      -
    • onclick for inputs with the checkbox type
    • -
    • oninput for other inputs and textareas
    • -
    • onchange for anything else
    • -
    -

    You can still declare an attribute with the same bound event.

    -

    Events will not override the bound event, instead, it will be executed after bind mutates the variable.

    -

    The new value will be merged into the function context.

    -
    import Nullstack from 'nullstack';
    -
    -class Form extends Nullstack {
    -
    -  name = '';
    -
    -  compare({value}) {
    -    this.name === value;
    -  }
    - 
    -  render() {
    -    return (
    -      <input bind={this.name} oninput={this.compare} />
    -    )
    -  }
    -
    -}
    -
    -export default Form;
    -
    -

    Bind source

    Bind can take a source attribute as well.

    -
    -

    💡 If you do not declare a source to the bind, Nullstack will inject a source={this} at transpile time in order to completely skip the runtime lookup process!

    -
    -

    If you declare a source, bind must be a string with the name of the key that will be mutated.

    -

    The source will be merged into the context of events.

    -
    import Nullstack from 'nullstack';
    -
    -class Paginator extends Nullstack {
    -
    -  validate({source, params}) {
    -    if(!source.page) {
    -      params.page = '1';
    -    }
    -  }
    -
    -  render({params}) {
    -    return (
    -      <input 
    -        source={params}
    -        bind="page"
    -        oninput={this.validate}
    -      />
    -    )
    -  }
    -
    -}
    -
    -export default Paginator;
    -
    -
    -

    💡 Binding by reference is possible because all binds are converted to the format above at transpile time.

    -
    -

    Any object that responds to a key call with "[]" can be bound.

    -

    The name attribute can be overwritten.

    -
    import Nullstack from 'nullstack';
    -
    -class Form extends Nullstack {
    -
    -  number = 1;
    -  boolean = true;
    -  character = 'a';
    -  text = 'aaaa';
    -  
    -  object = {count: 1};
    -  array = ['a', 'b', 'c'];
    -
    -  render({params}) {
    -    return (
    -      <div>
    -        <input bind={this.number} />
    -        <textarea bind={this.text} />
    -        <select bind={this.character}>
    -          {this.array.map((character) => <option>{character}</option>)}
    -        </select>
    -        <select bind={this.boolean} name="boolean-select">
    -          <option value={true}>true</option>
    -          <option value={false}>false</option>
    -        </select>
    -        <input bind={this.boolean} type="checkbox" />
    -        <input bind={this.object.count} />
    -        {this.array.map((value, index) => (
    -          <input bind={this.array[index]} />
    -        ))}
    -        <input bind={params.page} />
    -      </div>
    -    )
    -  }
    -
    -}
    -
    -export default Form;
    -
    -

    Object Events

    You can use object events alongside bind normally.

    -

    The event will run after the variable is mutated.

    -

    The event will share the bind source.

    -
    import Nullstack from 'nullstack';
    -
    -class Paginator extends Nullstack {
    -
    -  render({params}) {
    -    return (
    -      <input bind={params.filter} oninput={{page: 1}} />
    -    )
    -  }
    -
    -}
    -
    -export default Paginator;
    -
    -

    Bindable Components

    You can create your own bindable component by receiving the attributes that bind generates.

    -

    You must respond by calling onchange with a value key.

    -

    You can also merge any other keys you wish to send to the component user.

    -
    class CurrencyInput extends Nullstack {
    -
    -  parse({event, onchange}) {
    -    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 bringsHappyness = value >= 1000000;
    -    onchange({value, bringsHappyness});
    -  }
    -
    -  render({value, name}) {
    -    const formatted = value.toFixed(2).replace('.', ',');
    -    return <input name={name} value={formatted} oninput={this.parse} />
    -  }
    -
    -}
    -
    -
    import Nullstack from 'nullstack';
    -import CurrencyInput from './CurrencyInput';
    -
    -class Form extends Nullstack {
    -
    -  balance = 0;
    -
    -  render() {
    -    return (
    -      <CurrencyInput bind={this.balance} />
    -    )
    -  }
    -
    -}
    -
    -export default Form;
    -
    -

    Next step

    -

    🎉 Congratulations. You are done with the core concepts!

    -
    -

    ⚔ Learn about the application startup.

    -
    - - - \ No newline at end of file diff --git a/docs/two-way-bindings/index.json b/docs/two-way-bindings/index.json deleted file mode 100644 index 06d5886a..00000000 --- a/docs/two-way-bindings/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.7":{"title":"Two-Way Binding","html":"

    Big chunks of code in a progressive web application is dedicated to reacting to user input.

    \n

    The process of controlling user input can be broken into 3 tedious steps:

    \n
      \n
    • Declaring a variable with the initial value;
    • \n
    • Passing the initial value to the input;
    • \n
    • Observing changes in the input and assigning the new value to the variable.
    • \n
    \n

    The last step might include typecasting and other value treatments.

    \n

    This process in which you manually do all these steps is called one-way binding, it is the default in many frameworks, and is possible in Nullstack.

    \n
    import Nullstack from 'nullstack';\n\nclass Form extends Nullstack {\n\n  number = 1;\n  string = '';\n\n  updateString({event}) {\n    this.string = event.target.value;\n  }\n\n  updateNumber({event}) {\n    this.number = parseInt(event.target.value);\n  }\n \n  render() {\n    return (\n      <form>\n        <input\n          type=\"text\"\n          name=\"string\"\n          value={this.string}\n          oninput={this.updateString}\n        />\n        <input\n          type=\"number\"\n          name=\"number\"\n          value={this.number}\n          oninput={this.updateNumber}\n        />\n      </form>\n    )\n  }\n\n}\n\nexport default Form;\n
    \n

    The bind attribute

    Bind reduces drastically the amount of glue code you have to type in your application.

    \n

    You can shortcut setting a value, name, and event with the bind attribute.

    \n
    \n

    💡 Nullstack will simply replace bind with the value, name, and event under the hood.

    \n
    \n

    Bind will generate an event that automatically typecasts to the previous primitive type the value was.

    \n

    You can pass any variable to the bind as long as its parent object is mentioned.

    \n
    import Nullstack from 'nullstack';\n\nclass Form extends Nullstack {\n\n  number = 1;\n  string = '';\n \n  render() {\n    return (\n      <form>\n        <input type=\"text\" bind={this.string} />\n        <input type=\"number\" bind={this.number} />\n      </form>\n    )\n  }\n\n}\n\nexport default Form;\n
    \n

    Bound Events

    The following events are set for each type of input:

    \n
      \n
    • onclick for inputs with the checkbox type
    • \n
    • oninput for other inputs and textareas
    • \n
    • onchange for anything else
    • \n
    \n

    You can still declare an attribute with the same bound event.

    \n

    Events will not override the bound event, instead, it will be executed after bind mutates the variable.

    \n

    The new value will be merged into the function context.

    \n
    import Nullstack from 'nullstack';\n\nclass Form extends Nullstack {\n\n  name = '';\n\n  compare({value}) {\n    this.name === value;\n  }\n \n  render() {\n    return (\n      <input bind={this.name} oninput={this.compare} />\n    )\n  }\n\n}\n\nexport default Form;\n
    \n

    Bind source

    Bind can take a source attribute as well.

    \n
    \n

    💡 If you do not declare a source to the bind, Nullstack will inject a source={this} at transpile time in order to completely skip the runtime lookup process!

    \n
    \n

    If you declare a source, bind must be a string with the name of the key that will be mutated.

    \n

    The source will be merged into the context of events.

    \n
    import Nullstack from 'nullstack';\n\nclass Paginator extends Nullstack {\n\n  validate({source, params}) {\n    if(!source.page) {\n      params.page = '1';\n    }\n  }\n\n  render({params}) {\n    return (\n      <input \n        source={params}\n        bind=\"page\"\n        oninput={this.validate}\n      />\n    )\n  }\n\n}\n\nexport default Paginator;\n
    \n
    \n

    💡 Binding by reference is possible because all binds are converted to the format above at transpile time.

    \n
    \n

    Any object that responds to a key call with "[]" can be bound.

    \n

    The name attribute can be overwritten.

    \n
    import Nullstack from 'nullstack';\n\nclass Form extends Nullstack {\n\n  number = 1;\n  boolean = true;\n  character = 'a';\n  text = 'aaaa';\n  \n  object = {count: 1};\n  array = ['a', 'b', 'c'];\n\n  render({params}) {\n    return (\n      <div>\n        <input bind={this.number} />\n        <textarea bind={this.text} />\n        <select bind={this.character}>\n          {this.array.map((character) => <option>{character}</option>)}\n        </select>\n        <select bind={this.boolean} name=\"boolean-select\">\n          <option value={true}>true</option>\n          <option value={false}>false</option>\n        </select>\n        <input bind={this.boolean} type=\"checkbox\" />\n        <input bind={this.object.count} />\n        {this.array.map((value, index) => (\n          <input bind={this.array[index]} />\n        ))}\n        <input bind={params.page} />\n      </div>\n    )\n  }\n\n}\n\nexport default Form;\n
    \n

    Object Events

    You can use object events alongside bind normally.

    \n

    The event will run after the variable is mutated.

    \n

    The event will share the bind source.

    \n
    import Nullstack from 'nullstack';\n\nclass Paginator extends Nullstack {\n\n  render({params}) {\n    return (\n      <input bind={params.filter} oninput={{page: 1}} />\n    )\n  }\n\n}\n\nexport default Paginator;\n
    \n

    Bindable Components

    You can create your own bindable component by receiving the attributes that bind generates.

    \n

    You must respond by calling onchange with a value key.

    \n

    You can also merge any other keys you wish to send to the component user.

    \n
    class CurrencyInput extends Nullstack {\n\n  parse({event, onchange}) {\n    const normalized = event.target.value.replace(',', '').padStart(3, '0');\n    const whole = (parseInt(normalized.slice(0,-2)) || 0).toString();\n    const decimal = normalized.slice(normalized.length - 2);\n    const value = parseFloat(whole+'.'+decimal);\n    const bringsHappyness = value >= 1000000;\n    onchange({value, bringsHappyness});\n  }\n\n  render({value, name}) {\n    const formatted = value.toFixed(2).replace('.', ',');\n    return <input name={name} value={formatted} oninput={this.parse} />\n  }\n\n}\n
    \n
    import Nullstack from 'nullstack';\nimport CurrencyInput from './CurrencyInput';\n\nclass Form extends Nullstack {\n\n  balance = 0;\n\n  render() {\n    return (\n      <CurrencyInput bind={this.balance} />\n    )\n  }\n\n}\n\nexport default Form;\n
    \n

    Next step

    \n

    🎉 Congratulations. You are done with the core concepts!

    \n
    \n

    ⚔ Learn about the application startup.

    \n","description":"Bind reduces drastically the amount of glue code you have to type in your application."},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Two-Way Binding - Nullstack","description":"Bind reduces drastically the amount of glue code you have to type in your application."}} \ No newline at end of file diff --git a/docs/waifu.png b/docs/waifu.png deleted file mode 100644 index 2154b9b7..00000000 Binary files a/docs/waifu.png and /dev/null differ diff --git a/docs/waifu/index.html b/docs/waifu/index.html deleted file mode 100644 index 3448c846..00000000 --- a/docs/waifu/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - Nulla-Chan - Nullstack - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Nulla-Chan

    Nulla - Chan

    Nullstack's official waifu

    • 🕐 Age: 19
    • ♒ Sign: Aquarius
    • 🎂 Birthday: January 28
    • 💖 Blood Type: A
    • 📏 Height: 1.55m
    • 🍨 Fav Food: Anything vanilla flavored
    • 🧩 Hobby: Reinventing Wheels
    • 🧠 Neurodivergences: ASD and ADHD
    🎨 Created by: Bilkaya Check out the Character Concept

    A sweet and shy perfect waifu, is always hyperfocused in her studies but somehow distracted at the same time.

    She is always cheerful... until she has to face glue code or sees a post telling her which tech she should use.

    - - - \ No newline at end of file diff --git a/docs/waifu/index.json b/docs/waifu/index.json deleted file mode 100644 index fde22fa6..00000000 --- a/docs/waifu/index.json +++ /dev/null @@ -1 +0,0 @@ -{"instances": {"_.0":{},"_.0.0.1":{"expanded":false},"_.0.0.6":{},"_.0.0.8":{},"_.0.0.9":{}}, "page": {"image":"/image-1200x630.png","status":200,"locale":"en","title":"Nulla-Chan - Nullstack","description":"Nullstack's official waifu"}} \ No newline at end of file diff --git a/i18n/en-US/articles/404.md b/i18n/en-US/articles/404.md new file mode 100644 index 00000000..6b0a5e7e --- /dev/null +++ b/i18n/en-US/articles/404.md @@ -0,0 +1,9 @@ +--- +title: Page Not Found +description: Sorry, this is not the page you are looking for. +status: 404 +--- + +Perhaps you want to learn about [how to make a 404 page with Nullstack](/context-page)? + +If you are looking for something else, you should [read the documentation](/documentation). \ No newline at end of file diff --git a/i18n/en-US/articles/application-startup.md b/i18n/en-US/articles/application-startup.md new file mode 100644 index 00000000..c1c03a98 --- /dev/null +++ b/i18n/en-US/articles/application-startup.md @@ -0,0 +1,65 @@ +--- +title: Application Startup +description: The start function will run only once when your application loads and is a good place for setting up your context +action: ⚔ Learn about the [application startup](/application-startup). +--- + +The **server.js**/**client.js** files at your application root are responsible for starting your application. + +When you run the application with `npm start` the `Nullstack.start` method in both files will start your main component and return the [`context`](/context) object of their respectives environments. + +The returned `context` could be used normally, and you can set it's `start` method which runs only once, being a good place for setting things up, as your database: + +```jsx +import Nullstack from 'nullstack'; +import Application from './src/Application'; +import startDatabase from './database'; + +const context = Nullstack.start(Application); + +context.start = async function() { + context.database = await startDatabase(context.secrets); +} + +export default context; +``` + +> 💡 The `context.start` in **server.js** runs when the application is booted, and on **client.js** once the browser loads it + +The `context` can be updated in any way as long as it be exported on both files, when building the app Nullstack turns it into a serverless function out-of-box. + +## Dependency startup pattern + +A nice pattern to work with dependencies that require startup time configurations is to define a `_start` function in the dependency: + +```jsx +import Nullstack from 'nullstack'; + +class Dependency extends Nullstack { + + static async _start(context) { + // start something with context + } + +} + +export default Dependency; +``` + +And call it in the `context.start` passing the [context](/context): + +```jsx +import Nullstack from 'nullstack'; +import Application from './src/Application'; +import Dependency from './src/Dependency'; + +const context = Nullstack.start(Application); + +context.start = async function() { + await Dependency._start(context); +} + +export default Application; +``` + +> 🔒 Server functions with the name starting with "_" do not generate an API endpoint to avoid malicious API calls. \ No newline at end of file diff --git a/i18n/en-US/articles/context-data.md b/i18n/en-US/articles/context-data.md new file mode 100644 index 00000000..28a49c8d --- /dev/null +++ b/i18n/en-US/articles/context-data.md @@ -0,0 +1,47 @@ +--- +title: Context Data +description: The data object is a proxy in the Component Context available in client and gives you information about the element dataset +--- + +- Type: `object` +- Origin: [Component Context](/context#----component-context) +- Availability: **client** +- **readonly** in **client** context + +It gives you information about the element dataset. + +You can use this key to avoid polluting your DOM with invalid attributes. + +> 💡 This helps Nullstack set attributes without wasting time validating them. + +Any `data-*` attributes will receive a respective camelized key on the `data` object when passed to an event context. + +The kebab version is also available in the context. + +```jsx +import Nullstack from 'nullstack'; + +class ContextData extends Nullstack { + + count = 1; + + calculate({data}) { + this.count = this.count * data.multiply + data.sum; + } + + render({data}) { + return ( +
    + +
    + ) + } + +} + +export default ContextData; +``` + +> 💡 Camelized keys from the `data` object will result in kebab attributes in the DOM. \ No newline at end of file diff --git a/i18n/en-US/articles/context-environment.md b/i18n/en-US/articles/context-environment.md new file mode 100644 index 00000000..b9ab3c59 --- /dev/null +++ b/i18n/en-US/articles/context-environment.md @@ -0,0 +1,71 @@ +--- +title: Context Environment +description: The environment object is a proxy in the Nullstack Context available in both client and server and gives you information about the current environment +--- + +- Type: `object` +- Origin: [Nullstack Context](/context#----nullstack-context) +- Availability: server/client +- **readonly** in server/client context + +It gives you information about the current environment. + +The following keys are available in the object: + +- **client**: `boolean` +- **server**: `boolean` +- **development**: `boolean` +- **production**: `boolean` +- **static**: `boolean` +- **key**: `string` +- **hot** `boolean` + +```jsx +import Nullstack from 'nullstack'; + +class Page extends Nullstack { + + render({environment}) { + return ( +
    + {environment.client &&

    I'm in the client

    } + {environment.server &&

    I'm in the server

    } + {environment.development &&

    I'm in development mode

    } + {environment.production &&

    I'm in production mode

    } + {environment.static &&

    I'm in a static site

    } +

    My key is {environment.key}

    +
    + ) + } + +} + +export default Page; +``` + +The environment *key* is an md5 hash of the current environment folder outputs. The key is appended to [assets](/styles) and [static API](/static-site-generation) path to assist cache control. + +The environment *hot* is boolean that identifies if hot reload is enabled and is available only in development mode. + +## Custom Events + +During development any updates to tracked files will raise a custom event you can use to facilitate development flow. + +You can use this event to improve the developer experience by creating custom side effects to changes, like reinitiating specific components that need to reload data when code changes. + +```jsx +import Nullstack from 'nullstack'; + +class BlogArticle extends Nullstack { + + hydrate({environment}) { + if(!environment.hot) return + window.addEventListener(environment.event, () => this.initiate()); + } + +} + +export default BlogArticle; +``` + +> 🔥 `environment.event` is only available in client functions/lifecycles. \ No newline at end of file diff --git a/i18n/en-US/articles/context-instances.md b/i18n/en-US/articles/context-instances.md new file mode 100644 index 00000000..932412f2 --- /dev/null +++ b/i18n/en-US/articles/context-instances.md @@ -0,0 +1,93 @@ +--- +title: Context Instances +description: The instances object is a proxy in the Nullstack Context available in the client and gives you all active instances in the application +--- + +- Type: `object` +- Origin: [Nullstack Context](/context#----nullstack-context) +- Availability: **client** +- **readwrite** in **client** context + +It gives you all active instances of the application. + +> 💡 Active instances are the ones created and not yet [terminated](/full-stack-lifecycle#terminate) + +As explained in [instance `key`](/instance-self#instance-key), keys play a big role in defining a unique identifier for components. + +> 🔥 Nullstack trusts its developers to know what they are doing and exposes as much internal behavior for the programmer to do as it wishes, use with caution. + +The default key for class components is a join between the class name and the dom depth like `Counter/1.1` and are hard for humans to interact with. + +The first component of your application will have the key `application` by default. + +> 💡 keys define which instance will be used to render the component, you can use it to force a node to reinstantiate. + +Adding an unique `key` to **Counter** makes it easier to access on `instances` list: + +```jsx +import Nullstack from 'nullstack'; +import Counter from './Counter'; +import AnyOtherComponent from './AnyOtherComponent'; + +class Application extends Nullstack { + + render() { + return ( +
    + + +
    + ) + } + +} + +export default Application; +``` + +```jsx +import Nullstack from 'nullstack'; + +class Counter extends Nullstack { + + value = 0; + + increment() { + this.value++; + } + + render() { + return

    Count: {this.value}

    + } + +} + +export default Counter; +``` + +You can access any methods and instance variables from **counter** instance on **AnyOtherComponent**: + +```jsx +import Nullstack from 'nullstack'; + +class AnyOtherComponent extends Nullstack { + + render({ instances }) { + return ( + + ) + } + +} + +export default AnyOtherComponent; +``` + +The use of `instances` unlocks unlimited custom behaviors like: + +- A notification icon at the navbar that can be updated from other components at certain actions +- A *toast* component that can be invoked from anywhere in your application +- A *store* system with custom dispatch methods similar to Redux +- Something we haven't even imagined, dream on and post your ideas on GitHub! \ No newline at end of file diff --git a/i18n/en-US/articles/context-page.md b/i18n/en-US/articles/context-page.md new file mode 100644 index 00000000..351786e9 --- /dev/null +++ b/i18n/en-US/articles/context-page.md @@ -0,0 +1,136 @@ +--- +title: Context Page +description: The page object is a proxy in the Nullstack Context available in both client and server and gives you information about the document head metatags +--- + +- Type: `object` +- Origin: [Nullstack Context](/context#----nullstack-context) +- Availability: server/client +- **readwrite** in server/client context + +It gives you information about the document `head` metatags. + +`page` keys will be used to generate metatags during [server-side rendering](/server-side-rendering) and must be assigned before [`initiate`](/full-stack-lifecycle) while this resolved. + +The following keys are available in the object: + +- **title**: `string` +- **image**: `string` (absolute or relative URL) +- **description**: `string` +- **canonical**: `string` (absolute or relative URL) +- **locale**: `string` +- **robots**: `string` +- **schema**: `object` +- **changes**: `string` +- **priority**: `number` +- **status**: `number` + +When the `title` key is assigned on the client-side, the document title will be updated. + +Nullstack uses the `changes` and `priority` keys to generate the **sitemap.xml**. + +The sitemap is generated automatically only when using [static site generation](/static-site-generation) and must be manually generated in [server-side rendered](/server-side-rendering) applications. + +The `changes` key represents the `changefreq` key in the **sitemap.xml** and if assigned must be one of the following values: + +- **always** +- **hourly** +- **daily** +- **weekly** +- **monthly** +- **yearly** +- **never** + +The `priority` key is a number between `0.0` and `1.0` that represents the `priority` key in the **sitemap.xml**. + +Nullstack does not set a default priority, however, sitemaps assume a `0.5` priority when not explicitly set. + +Besides `title` and `locale` all other keys have sensible defaults generated based on the application scope. + +```jsx +import Nullstack from 'nullstack'; + +class Page extends Nullstack { + + prepare({project, page}) { + page.title = `${project.name} - Page Title`; + page.image = '/image.jpg'; + page.description = 'Page meta description'; + page.canonical = 'http://absolute.url/canonical-link'; + page.locale = 'pt-BR'; + page.robots = 'index, follow'; + page.schema = {}; + page.changes = 'weekly'; + page.priority = 1; + } + + render({page}) { + return ( +
    +

    {page.title}

    +

    {page.description}

    +
    + ) + } + +} + +export default Page; +``` + +## Custom Events + +Updating `page.title` will raise a custom event. + +```jsx +import Nullstack from 'nullstack'; + +class Analytics extends Nullstack { + + hydrate({page}) { + window.addEventListener(page.event, () => { + console.log(`New title: ${page.title}`); + }); + } + +} + +export default Analytics; +``` + +> 🔥 `page.event` is only available in client functions/lifecycles. + +## Error pages + +If during the [server-side render](/server-side-rendering) process the `page.status` has any value besides `200`, your application will receive another render pass that gives you the chance to adjust the interface according to the status. + +The `status` key will be raised with the HTTP response. + +The page status will be modified to `500` and receive another render pass if the page raise an exception while rendering. + +The status of [server functions](/server-functions) responses will be set to the `page.status`. + +```jsx +import Nullstack from 'nullstack'; +import ErrorPage from './ErrorPage'; +import HomePage from './HomePage'; + +class Application extends Nullstack { + + // ... + + render({page}) { + return ( +
    + {page.status !== 200 && } + +
    + ) + } + +} + +export default Application; +``` + +> 🔥 Assigning to the `status` key during the [single-page application](/full-stack-lifecycle) mode will have no effect. \ No newline at end of file diff --git a/i18n/en-US/articles/context-project.md b/i18n/en-US/articles/context-project.md new file mode 100644 index 00000000..12ac9f2d --- /dev/null +++ b/i18n/en-US/articles/context-project.md @@ -0,0 +1,102 @@ +--- +title: Context Project +description: The project object is a proxy in the Nullstack Context available in both client and server and gives you information about the app manifest and some metatags +--- + +- Type: `object` +- Origin: [Nullstack Context](/context#----nullstack-context) +- Availability: server/client +- **readwrite** in the **server** context +- **readonly** in the **client** context + +It gives you information about the app manifest and some metatags. + +`project` keys will be used to generate metatags during server-side rendering and must be assigned before [`initiate`](/full-stack-lifecycle) is resolved. + +`project` keys will be used to generate the app **manifest**. + +The `disallow` key will be used to generate the **robots.txt**. + +The following keys are available in the object and supported as environment variables as follows: + +- **domain**: `string` (`NULLSTACK_PROJECT_DOMAIN`) +- **name**: `string` (`NULLSTACK_PROJECT_NAME`) +- **shortName**: `string` (`NULLSTACK_PROJECT_SHORT_NAME`) +- **color**: `string` (`NULLSTACK_PROJECT_COLOR`) +- **backgroundColor**: `string` +- **type**: `string` +- **display**: `string` +- **orientation**: `string` +- **scope**: `string` +- **root**: `string` +- **icons**: `object` +- **favicon**: `string` (relative or absolute url) +- **disallow**: `string array` (relative paths) +- **sitemap**: `boolean` or `string` (relative or absolute url) +- **cdn**: `string` (`NULLSTACK_PROJECT_CDN`) +- **protocol**: `string` (`NULLSTACK_PROJECT_PROTOCOL`) + +Besides `domain`, `name` and `color` all other keys have sensible defaults generated based on the application scope. + +If you do not declare the `icons` key, Nullstack will scan any icons with the name following the pattern "icon-[WIDTH]x[HEIGHT].png" in your **public** folder. + +The `head` meta tag `apple-touch-icon` will be set to your `icon-180x180.png` file. + +If the `sitemap` key is set to true your **robots.txt** file will point the sitemap to `https://${project.domain}/sitemap.xml`. + +The `cdn` key will prefix your asset bundles and will be available in the context so you can manually prefix other assets. + +The `protocol` key is "http" in development mode and "https" in production mode by default. + +```jsx +// server.js +import Nullstack from 'nullstack'; +import Application from './src/Application'; + +const context = Nullstack.start(Application); + +context.start = function() { + const { project } = context; + project.name = 'Nullstack'; + project.shortName = 'Nullstack'; + project.domain = 'nullstack.app'; + 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', + '128': '/icon-128x128.png', + '512': '/icon-512x512.png' + }; + project.favicon = '/favicon.png'; + project.disallow = ['/admin']; + project.sitemap = true; + project.cdn = 'cdn.nullstack.app'; + project.protocol = 'https'; +} + +export default context; +``` + +> More about the `context.start` at [application startup](/application-startup) + +```jsx +// src/Application.njs +import Nullstack from 'nullstack'; + +class Application extends Nullstack { + + prepare({project, page}) { + page.title = project.name; + } + +} + +export default Application; +``` + +> 💡 You can override the automatically generated **manifest.json** and **robots.txt** by serving your own file from the **public** folder \ No newline at end of file diff --git a/i18n/en-US/articles/context-secrets.md b/i18n/en-US/articles/context-secrets.md new file mode 100644 index 00000000..3c52fc79 --- /dev/null +++ b/i18n/en-US/articles/context-secrets.md @@ -0,0 +1,55 @@ +--- +title: Context Secrets +description: The secrets object is a proxy in the Nullstack Context available in server which you can use to configure your application with private information +--- + +- Type: `object` +- Origin: [Nullstack Context](/context#----nullstack-context) +- Availability: **server** +- **readwrite** in **server** context + +You can use it to configure your application with private information. + +You can assign any keys with any type to the object. + +You can assign keys to `secrets` dynamically based on current environment using [`context.environment`](/context-environment). + +```jsx +// server.js +import Nullstack from 'nullstack'; +import Application from './src/Application'; + +const context = Nullstack.start(Application); + +context.start = function() { + const { secrets, environment } = context; + secrets.endpoint = 'https://domain.com/api'; + secrets.privateKey = environment.development ? 'DEV_API_KEY' : 'PROD_API_KEY'; +} + +export default context; +``` + +```jsx +// src/Application.njs +import Nullstack from 'nullstack'; + +class Application extends Nullstack { + + static async fetchFromApi({secrets}) { + const response = await fetch(secrets.endpoint, { + headers: { + Authorization: `Bearer ${secrets.privateKey}` + } + }); + return await response.json(); + } + +} + +export default Application; +``` + +Any environment variable starting with NULLSTACK_SECRETS_ will be mapped to the `secrets` in that environment. + +> 🐱‍💻 NULLSTACK_SECRETS_PRIVATE_KEY will be mapped to `secrets.privateKey` \ No newline at end of file diff --git a/i18n/en-US/articles/context-settings.md b/i18n/en-US/articles/context-settings.md new file mode 100644 index 00000000..fb45ae00 --- /dev/null +++ b/i18n/en-US/articles/context-settings.md @@ -0,0 +1,56 @@ +--- +title: Context Settings +description: The settings object is a proxy in the Nullstack Context available in both client and server which you can use to configure your application with public information +--- + +- Type: `object` +- Origin: [Nullstack Context](/context#----nullstack-context) +- Availability: server/client +- **readwrite** in **server** context +- **readonly** in **client** context + +You can use it to configure your application with public information. + +You can assign any keys with any type to the object. + +You can assign keys to `settings` dynamically based on current environment using [`context.environment`](/context-environment). + +```jsx +// server.js +import Nullstack from 'nullstack'; +import Application from './src/Application'; + +const context = Nullstack.start(Application); + +context.start = function() { + const { settings, environment } = context; + settings.endpoint = 'https://domain.com/api'; + settings.publicKey = environment.development ? 'DEV_API_KEY' : 'PROD_API_KEY'; +} + +export default context; +``` + +```jsx +// src/Application.njs +import Nullstack from 'nullstack'; + +class Application extends Nullstack { + + async hydrate({settings}) { + const response = await fetch(settings.endpoint, { + headers: { + Authorization: `Bearer ${settings.publicKey}` + } + }); + this.data = await response.json(); + } + +} + +export default Application; +``` + +Any environment variable starting with NULLSTACK_SETTINGS_ will be mapped to the `settings` in that environment. + +> 🐱‍💻 NULLSTACK_SETTINGS_PUBLIC_KEY will be mapped to `settings.publicKey` \ No newline at end of file diff --git a/articles/context.md b/i18n/en-US/articles/context.md similarity index 62% rename from articles/context.md rename to i18n/en-US/articles/context.md index 76b06ee6..dd39286f 100644 --- a/articles/context.md +++ b/i18n/en-US/articles/context.md @@ -1,6 +1,6 @@ --- title: Context -description: Create full-stack javascript applications within seconds +description: Every function in Nullstack receives a context as the argument. --- Every function in Nullstack receives a context as the argument. @@ -13,43 +13,38 @@ The server context lives as long as the server is running. Both contexts are proxies that merge the keys of 3 objects: -## 1 - Framework store +## 1 - Nullstack Context These are the information that the framework makes available to you by default. -### The available global server keys are: +### The available global keys in both server and client are: -- [page](/context-page) -- [environment](/context-environment) -- [project](/context-project) -- [server](/server-request-and-response) -- [request](/server-request-and-response) -- [response](/server-request-and-response) -- [worker](/service-worker) +- [`page`](/context-page) +- [`project`](/context-project) +- [`environment`](/context-environment) +- [`params`](/routes-and-params#params) +- [`router`](/routes-and-params#router) +- [`settings`](/context-settings) +- [`worker`](/service-worker) -### The available global client keys are: +### The keys available only in server functions are: -- [data](/context-data) -- [page](/context-page) -- [project](/context-project) -- [environment](/context-environment) -- [params](/routes-and-params) -- [router](/routes-and-params) -- [worker](/service-worker) +- [`server`](/server-request-and-response) +- [`request`](/server-request-and-response#request-and-response) +- [`response`](/server-request-and-response#request-and-response) +- [`secrets`](/context-secrets) -### The available instance client keys are: +### The key available only in client: -- [self](/instance-self) -- [children](/renderable-components) -- [key](/instance-key) +- [`instances`](/context-instances) -## 2 - Application store +## 2 - Application Context When you set a key to the context it will be available for destructuring at any depth of the application, even the parents of your component or 3rd party applications that mount your component. Updating a key in the context causes the application to re-render automatically. -You can think of this as a single concept to replace stores, contexts, services, and reducers at the same time using the dependency injection pattern with vanilla javascript objects instead. +You can think of this as a single concept to replace **stores**, **contexts**, **services**, and **reducers** at the same time using the dependency injection pattern with vanilla JavaScript objects instead. ```jsx import Nullstack from 'nullstack'; @@ -61,14 +56,14 @@ class Counter extends Nullstack { } static async updateTotalCount(context) { - context.totalCount += count; + context.totalCount += context.count; } async double(context) { context.count += context.count; - await this.updateTotalCount({count: context.count}); + await this.updateTotalCount(); } - + render({count}) { return ( @@ -86,10 +81,10 @@ import Counter from './Counter'; class Application extends Nullstack { - static async start(context) { + prepare(context) { context.totalCount = 0; } - + render({count}) { return (
    @@ -103,9 +98,13 @@ class Application extends Nullstack { export default Application; ``` -## 3 - Attributes +## 3 - Component Context -These are the attributes you declare in your tag. +This one contains the attributes you declare in your tag, and including: + +- [`data`](/context-data) +- [`self`](/instance-self) +- [`children`](/jsx-elements#components-with-children) If the attribute is declared in a component tag every function of that component will have access to that attribute in its context. @@ -119,10 +118,10 @@ class Counter extends Nullstack { add(context) { context.count += context.delta + context.amount; } - + render({count, delta}) { return ( - ) @@ -142,11 +141,11 @@ class Application extends Nullstack { prepare(context) { context.count = 0; } - + render() { return (
    - } +
    ) } @@ -167,20 +166,21 @@ import Nullstack from 'nullstack'; class Counter extends Nullstack { - prepare() { - this.add(); - this.add({amount: 2}); - } - add(context) { context.count += context.amount || 1; } -} + prepare(context) { + context.count = 0; + this.add(); // sums 1 + this.add({amount: 2}); // sums 2 + } -export default Counter; -``` + async initiate(context) { + console.log(context.count); // 3 + } -## Next step +} -⚔ Learn about [routes and params](/routes-and-params). \ No newline at end of file +export default Counter; +``` \ No newline at end of file diff --git a/i18n/en-US/articles/frequently-asked-questions.md b/i18n/en-US/articles/frequently-asked-questions.md new file mode 100644 index 00000000..a1627f6b --- /dev/null +++ b/i18n/en-US/articles/frequently-asked-questions.md @@ -0,0 +1,63 @@ +--- +title: Frequently Asked Questions +description: Nullstack is an isomorphic JavaScript framework that allows developers to build Full Stack applications while staying focused on the product features, and solving the user problems, rather than spending a significant amount of their time worrying about layers of abstraction and choosing which tools make them look fancy +--- + +## What is feature-driven? +It means that every component is a full feature in itself, but it is up to you to decide how to break features down. As an example, you could have a "BlogPost" component or you could have a Blog a Post a Comment, and a Like component and they are nested into each other, heck you could even have an entire e-commerce application be a top-level component imported in another application, that's the beauty of full stack components. + +In the case of a Like button for instance, you would have the server functions to load the number of likes that item has, a render function to show your likes, and an event that triggers a server function to save your like, all in a single file if you want, the main reasoning for this architecture is that the project manager's tickets, the UX and the code look pretty much the same. + +*- But my bootcamp teacher said I need at least 3 layers of abstraction to be a good developer, can I do that in Nullstack?* + +Yes you can, Nullstack is a big foot-gun you can shoot your own foot in your favorite style, just ask for the permission of an adult first. + +## Is it a react framework? +Nope, but it uses JSX. Nullstack does not use hooks, instead, it aims to keep JavaScript's natural flow, but makes it reactive. Fun fact tho, Nullstack actually started as a react framework that just aimed to add server functions to the components, but eventually, I found that sticking to that model was limiting our creativity. + +## Why did you build another framework? +That wasn't the original intention, We were actually trying to build a coffee shop, but We get distracted very easily... you can learn more about the history behind Nullstack in [Anny's TDC talk](https://www.youtube.com/watch?v=77qeq6cSHG8). + +Initially, We just wanted to put the server code alongside the client code, because the projects We were working on had very similar patterns, and one of those patterns was that the clients would completely change their minds all the time, and your regular clean code kinda thing was not keeping up with the changes. + +I often noticed myself trying to fit into the format of the framework and changing features, or even avoiding creating new API endpoints because i didn't wanna make things to coupled or complicated. In Nullstack i just need to create a new function in the same class that just returns whatever data i need just for that case, which makes me a lot more productive and makes me think of what i'm building a lot more often than how i am building it. + +## Does it use a virtual dom or a compiler or signals? +The answer is yes. Nullstack has what I like to call a "Virtual Pipeline" it optimizes things differently in different scenarios for different environments. However that is just an implementation detail, the main thing is that it **just works** and We may change anything internal at any time. The only condition We have is that anything that works in vanilla JS should work in Nullstack, so We won't adopt any cool new trends if it limits the developer experience, or if the developer now needs to be aware of new edge cases instead of getting stuff done. + +## Does it uses Classes or Functions? +It uses whatever JavaScript code you can come up with. By default it follows the same paradigm as vanilla: functions are pure, and classes hold state. I know I will lose a lot of devs after the previous sentence, but keep in mind when people say "classes are bad" they actually mean "classes are bad on the current framework I'm using" . In Nullstack it just feels very natural, give it a try. + +## Is it blazingly fast? +Yes. But it is optimized for what I consider the "right type of fast", it's fast for the users. It uses a mix of architectures to optimize for different scenarios, but all of them aim to have the users have very responsive experiences, even when they are offline. + +You can always benchmark things and be excited about microseconds, but that all ends when your user 3g is not feeling like reaching your server that day. + +That being said, We love spending time micro benchmarking things and it's actually pretty fast, if you sneak into our commit history you will notice We went for "what We need to create products" first then We went "optimization happy", as you should if you plan to code professionally and not just watching clickbait. + +![Nullstack's documentation lighthouse score](/lighthouse.webp) + +## What can i build with it? Does it scale? +You can build anything you can build with regular JavaScript. We've built so far plenty of PWAs, blockchain applications across multiple blockchains (dApps), mobile games, capacitor applications, electron applications, chrome extensions, and even things to visualize [brain to computer interface](https://ae.studio/brain-computer-interface) data that require a lot of performance. Currently, We have a SaaS with hundreds of thousands of hits a day, and it is holding just fine on a "regular JavaScript-priced" server. + +## How can i use a new framework? it won't have an ecosystem. +Remember when I mentioned We keep everything vanilla just working? that makes most JavaScript packages for browser and server compatible with Nullstack out of the box, that's a pretty huge ecosystem in my opinion. But also if you are feeling like a mad scientist, you could always just install your favorite framework on top of a Nullstack's application by rendering to an element controlled by it. + +## Does it have any kind of telemetry? +No, We do not collect any data. Nullstack was not built as a framework first, it was extracted from real applications We worked on over the years, so the features it has are based on what We needed as opposite of what the data would point would cause better thumbnails. + +*- But how will you guys know what features We want then?* + +You can always tell us on discord or [create an issue on GitHub](https://github.com/nullstack/nullstack/issues), We will be happy to talk about it! + +## Does Nullstack have any social media? +Of the 6 main contributors of Nullstack 6 of them are autistic, including the person writing this wall of text... so you mostly lost us at the "social" part. We did try tho, and We created a [YouTube Channel](https://www.youtube.com/nullstack), a [Twitter](https://twitter.com/nullstackapp), and an [Instagram](https://www.instagram.com/nullstackapp/), but 4 of us also have ADHD so We ended up procrastinating and not posting on it, oopsie. We are pretty active on [Discord](https://discord.gg/eDZfKz264v) though, that's the place We are forced to log in anyway to coordinate our raids. + +## What is Nulla-Chan? +Nulla-Chan is the avatar of Nullstack, it belongs to a category of avatars called a "Waifu". If you don't know what a waifu is, that probably means that you have your life on the right track. Fun fact, the waifu character was created by the author's IRL waifu. + +## What was the hardest part creating a framework? +Definitively it was deciding if We should spell it "full stack", "fullstack", or "full-stack". Seriously please tell us on discord which one to pick. + +## How can i get started? +Just go to your favorite terminal and type "npx create-nullstack-app app-name" and use the templates available through the CLI. \ No newline at end of file diff --git a/articles/full-stack-lifecycle.md b/i18n/en-US/articles/full-stack-lifecycle.md similarity index 68% rename from articles/full-stack-lifecycle.md rename to i18n/en-US/articles/full-stack-lifecycle.md index 9f4ff43b..66dd2858 100644 --- a/articles/full-stack-lifecycle.md +++ b/i18n/en-US/articles/full-stack-lifecycle.md @@ -1,5 +1,5 @@ --- -title: Full-Stack Lifecycle +title: Full Stack Lifecycle description: Lifecycle methods are special named functions that you can declare in the class. --- @@ -13,7 +13,7 @@ This method is blocking and runs before the first time the component is rendered You can use this function to set the state that the user will see before things are loaded. -If the user is entering from this route *prepare* will run in the server before Nullstack [server-side renders](/server-side-rendering) your application. +If the user is entering from this route `prepare` will run in the server before Nullstack [server-side renders](/server-side-rendering) your application. If the user is navigating from another route this method will run in the client. @@ -41,12 +41,14 @@ This method can be async and runs right after the component is prepared and rend You can use this function to invoke another server function and load the data to present the page. -If the user is entering from this route *initiate* will run in the server. +If the user is entering from this route `initiate` will run in the server. Nullstack will wait till the promise is resolved and then finally generate the HTML that will be served. If the user is navigating from another route this method will run in the client. +After this method promise is fulfilled `this.initiated` will be set to true + ```jsx import Nullstack from 'nullstack'; @@ -60,7 +62,12 @@ class Component extends Nullstack { }); } - // ... + render() { + if(!this.initiated) return false + return ( +

    {this.task.description}

    + ) + } } @@ -69,6 +76,32 @@ export default Component; > ✨ Learn more about [server functions](/server-functions). +## Launch + +Runs before pre-rendering and at each awakening. + +You can update the component with things that doesn't require data fetching operations. + +> ✨ Use this lifecycle to setup Meta tags. + +```jsx +import Nullstack from 'nullstack'; + +class Component extends Nullstack { + + // ... + + launch({ page }) { + page.title = 'Very good title that considers SEO' + } + + // ... + +} + +export default Component; +``` + ## Hydrate This method is async and will only run in the client. @@ -77,6 +110,8 @@ This method will always run no matter which environment started the component. This is a good place to trigger dependencies that manipulate the dom or can only run on the client-side. +After this method promise is fulfilled `this.hydrated` will be set to true + ```jsx import Nullstack from 'nullstack'; @@ -90,7 +125,12 @@ class Component extends Nullstack { }, 1000); } - // ... + render() { + if(!this.hydrated) return false + return ( +

    timer id: {this.timer}

    + ) + } } @@ -103,11 +143,11 @@ This method is async and will only run in the client. This method runs on every component anytime the application state changes. -> 🔥 Be careful not to cause infinite loopings when mutating state inside *update*. +> 🔥 Be careful not to cause infinite loopings when mutating state inside `update`. This will run right before rendering but will not block the rendering queue. -The *update* function will not start running until the application is rendered after the initiate. +The `update` function will not start running until the application is rendered after the `initiate`. ```jsx import Nullstack from 'nullstack'; @@ -139,9 +179,11 @@ This method is async and will only run in the client. This method will run after your component leaves the DOM. -This is the place to clean up whatever you set up in the *hydrate* method. +This is the place to clean up whatever you set up in the `hydrate` method. + +The instance will be garbage collected after the `Promise` is resolved. -The instance will be garbage collected after the promise is resolved. +After this method promise is fulfilled `this.terminated` will be set to true which is useful in the case of [persistent components](/persistent-components) ```jsx import Nullstack from 'nullstack'; @@ -154,13 +196,15 @@ class Component extends Nullstack { clearInterval(this.timer); } + executeBackgroundTask() { + if(!this.terminated) { + // ... + } + } + // ... } export default Component; -``` - -## Next steps - -⚔ Learn about [server functions](/server-functions). \ No newline at end of file +``` \ No newline at end of file diff --git a/i18n/en-US/articles/getting-started.md b/i18n/en-US/articles/getting-started.md new file mode 100644 index 00000000..00102d3b --- /dev/null +++ b/i18n/en-US/articles/getting-started.md @@ -0,0 +1,103 @@ +--- +title: Getting Started +description: Create full stack JavaScript applications within seconds +action: ⚔ Learn [how to create a nullstack project](/getting-started). +--- + +> 📌 You can watch a video tutorial on our [Youtube Channel](https://www.youtube.com/watch?v=l23z00GEar8&list=PL5ylYELQy1hyFbguVaShp3XujjdVXLpId). + +Create full stack JavaScript applications within seconds using `npx` to generate your project files from the latest template. + +> 🔥 The minimum required [node.js](https://nodejs.org) version for development mode is *12.20.0*. + +Replace `project-name` with your project name and run the command below to start a project: + +```sh +npx create-nullstack-app@latest project-name +``` + +> 💡 You can use a CLI to select the blank javascript or typescript template or select the template with tailwind css. + +Change directory to the generated folder: + +```sh +cd project-name +``` + +Install the dependencies: + +```sh +npm install # or yarn +``` + +Start the application in development mode: + +```sh +npm start # or yarn start +``` + +## Understanding the generated files + +The following folders and files will be generated: + +### server.js + +This is the server entry and generator point. + +It is a convenient place to set up global things like [database](/how-to-use-mongodb-with-nullstack) and manipulate server `context`, details in [application startup](/application-startup). + +### client.js + +This is the client entry and generator point. + +It is a convenient place to import global dependencies like CSS frameworks and manipulate client `context`. + +### src/ + +This folder will contain the actual source code of your application. + +### src/Application.jsx + +This is your application main file. + +>✨ Learn more about the [jsx elements](/jsx-elements "Nullstack JavaScript"). + +When you run `npm start` it is consumed in **server**/**client** JS files by their `Nullstack.start` function, which starts and returns both [`context`](/context), that you can use to set up things like [database](/how-to-use-mongodb-with-nullstack) using [settings](/context-settings) and [secrets](/context-secrets). + +>✨ Learn more about the [application startup](/application-startup). + +#### TypeScript + +You can use Nullstack with TypeScript, just rename `njs` to `nts` or `jsx` to `tsx`. + +### src/Application.css + +This is an empty file just to demonstrate that you can use [CSS with Nullstack](/styles). + +It is a good practice to import a style file in a component with the same name. + +>✨ Learn more about [styles](/styles). + +### public/ + +Every file in here will be available to anyone from the domain root. + +By default `create-nullstack-app` generates the icons required for your **manifest.json** and images for OG meta tags. + +>✨ Learn more about [manifest.json](/context-project). + +Be sure to replace these images with your project identity. + +### .development/ + +This is the compiled result of your application in development mode. + +> 🔥 Do not touch this folder + +### .production/ + +This is the compiled result of your application in production mode. + +> 🔥 Do not touch this folder + +>✨ Learn more about [how to deploy a Nullstack application](/how-to-deploy-a-nullstack-application). \ No newline at end of file diff --git a/i18n/en-US/articles/how-to-customize-webpack.md b/i18n/en-US/articles/how-to-customize-webpack.md new file mode 100644 index 00000000..0a953a4f --- /dev/null +++ b/i18n/en-US/articles/how-to-customize-webpack.md @@ -0,0 +1,58 @@ +--- +title: How to customize Webpack +description: You can create your own custom webpack config to extend Nullstacks default configs +--- + +You can create your own custom `webpack.config.js` at the projects root folder to extend Nullstacks default configs + +Nullstack exposes the file `nullstack/webpack.config.js` which exports a server and client function, each being the config for the respective environment + +You can import nullstack webpack config with the following code + +```jsx +const [server, client] = require('nullstack/webpack.config'); + +module.exports = [server, client] +``` + +You can customize a single environment by wrapping the targeted function + +```jsx +const [server, client] = require('nullstack/webpack.config'); + +const glob = require('glob'); +const PurgecssPlugin = require('purgecss-webpack-plugin'); + +function customClient(...args) { + const config = client(...args); + if (config.mode === 'production') { + config.plugins.push(new PurgecssPlugin({ + paths: glob.sync(`src/**/*`, { nodir: true }), + content: ['./**/*.njs'], + safelist: ['script', 'body', 'html', 'style'], + defaultExtractor: content => content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || [], + })); + } + + return config; +} + +module.exports = [server, customClient] +``` + +You can also extend both environments at once by creating a wrapper around both environments + +```jsx +const [server, client] = require('nullstack/webpack.config'); +const CadencePlugin = require('cadence-webpack-plugin'); + +function applyCadencePlugin(environments) { + return environments.map((environment) => (...args) => { + const config = environment(...args); + config.plugins.push(new CadencePlugin()) + return config; + }) +} + +module.exports = applyCadencePlugin([server, client]) +``` \ No newline at end of file diff --git a/articles/how-to-deploy-a-nullstack-application.md b/i18n/en-US/articles/how-to-deploy-a-nullstack-application.md similarity index 54% rename from articles/how-to-deploy-a-nullstack-application.md rename to i18n/en-US/articles/how-to-deploy-a-nullstack-application.md index 9b033cb3..70212281 100644 --- a/articles/how-to-deploy-a-nullstack-application.md +++ b/i18n/en-US/articles/how-to-deploy-a-nullstack-application.md @@ -5,21 +5,21 @@ description: With Nullstack it's easy to have your application up and running in With Nullstack it's easy to have your application up and running in production mode. -> 🐱‍💻 *stonks* +> 🐱‍💻 **stonks** Nullstack compiles your code and all your dependencies using [Webpack](https://webpack.js.org). -The output of the compilation is moved to the *.production* folder and is the only folder besides *public* that needs to be moved into the host machine. +The output of the compilation is moved to the **.production** folder and is the only folder besides **public** that needs to be moved into the host machine. -If you have *project.cdn* set you must move the public folder to the actual cdn. +If you have `project.cdn` set you must move the **public** folder to the actual cdn. -> 💡 It is important that the *.production* folder is present for environment detection +> 💡 It is important that the **.production** folder is present for environment detection The host machine must have at least node v8.10.0 installed. You don't have to "npm install" in the host machine. -> ✨ You can configure the environment using [settings](/context-settings) and [secrets](/context-secrets) +> ✨ You can configure the environment using [`settings`](/context-settings) and [`secrets`](/context-secrets) To start the server just run: @@ -33,8 +33,8 @@ node .production/server.js After you [generate a static site](/static-site-generation), all you have to do is move the output folder to any host machine capable of serving HTML. -## Next step +## Examples -> 🎉 *Congratulations*. You are done with the advanced concepts! - -⚔ Learn [how to use MongoDB with Nullstack](/how-to-use-mongodb-with-nullstack). \ No newline at end of file +- [How to deploy to Vercel](/examples/how-to-deploy-to-vercel) +- [How to deploy to Github Pages](/examples/how-to-deploy-to-github-pages) +- [How to deploy to Heroku](/examples/how-to-deploy-to-heroku) \ No newline at end of file diff --git a/i18n/en-US/articles/jsx-elements.md b/i18n/en-US/articles/jsx-elements.md new file mode 100644 index 00000000..623278b4 --- /dev/null +++ b/i18n/en-US/articles/jsx-elements.md @@ -0,0 +1,290 @@ +--- +title: JSX elements +description: Nullstack JSX deviates a little from the spec. +--- + +## Using HTML attributes + +Nullstack JSX deviates a little from the spec. + +You can use the normal HTML attributes like `class` and `for` directly. + +```jsx + +``` + +## Headless components + +If you want to skip rendering the component at all you can simply return `false` from the render. + +```jsx +import Nullstack from 'nullstack'; + +class Headless extends Nullstack { + + render() { + return false; + } + +} + +export default Headless; +``` + +This will allocate DOM space for when you decide to render markup there. + +This is also useful for conditional rendering. + +If all you want to do is to generate an invisible component you can skip defining the render method at all. + +## Boolean attributes + +Attributes can be assigned as a boolean. + +When the value is `false` the attribute will not be rendered at all. + +When the value is `true` it will be rendered as a boolean attribute without a string value. + +```jsx + +``` + +You can shortcut attributes when you know the value will always be true. + +```jsx + +``` + +> ✨ Learn more about [attributes](/context). + +## Element tag + +If you need to decide the tag name at runtime, you can use the element tag and set the tag attribute conditionally. + +```jsx + + some arbitrary text + +``` + +When the tag attribute is omitted, Nullstack will default to a `div`. + +## Fragments + +Fragments are elements that renders it's contents in the parent component. + +```jsx +export default function Fragmented() { + return ( + <> + <> + + +

    Paragraph!

    + + ) +} +``` + +Wherever it is used, the above functional component will be rendered as follows: + +```html + +

    Paragraph!

    +``` + +## SVG Elements + +SVG can be used as if it were any regular HTML tag. + +You can manipulate the SVG using attributes and events normally. + +```jsx + + + +``` + +> ✨ Learn more about [events](/stateful-components). + +## Components with children + +Your component can be invoked passing a block of content. + +```jsx +
    +

    Hello World

    +
    +``` + +This doesn't automatically render the block since it wouldn't know where to place it. + +You can destructure the children on the render method and place it in your markup. + +```jsx +import Nullstack from 'nullstack'; + +class Header extends Nullstack { + + render({children}) { + return ( +
    {children}
    + ) + } + +} + +export default Header; +``` + +> ✨ This is possible because the `children` key is part of the [instance context](/context#the-available-instance-client-keys-are-). + +## Lists + +You can map over lists without declaring a `key`. + +> ✨ A key in Nullstack is an ID for a specific component instance. Learn more about the [instance key](/instance-self#instance-key). + +Lists that may change length must be wrapped in a parent element just for them. + +```jsx +
      + {list.map((item) =>
    • {item.name}
    • )} +
    +``` + +You can emulate a fixed-size list by returning `false` instead of an element to reserve dom space. + +```jsx +{list.map((item) => ( + item.visible ?
    {item.name}
    : false +)} +``` + +It's a nice practice to use inner components combined with lists to clean up your code. + +```jsx +import Nullstack from 'nullstack'; + +class List extends Nullstack { + + items = [ + {visible: true, number: 1}, + {visible: false, number: 2}, + {visible: true, number: 3} + ] + + renderItem({visible, number}) { + if(!visible) return false; + return ( +
  • {number}
  • + ) + } + + render() { + return ( +
      + {this.items.map((item) => )} +
    + ) + } + +} + +export default List; +``` + +## Inner HTML + +You can set the inner HTML of an element with the `html` attribute. + +Links inside the HTML string will be replaced with [routable anchors](/routes-and-params). + +```jsx +import Nullstack from 'nullstack'; + +class Post extends Nullstack { + + content = ` +

    This is a Post

    + + Check this other post + + `; + + render() { + return ( +
    + ) + } + +} + +export default Post; +``` + +> 🔥 Be careful! When using user-generated HTML you are in risk of script injection + +## Body tag + +Renderable components can render a `body` tag an unlimited number of times at any depth of the application. + +The `body` attributes of the body tag that are rendered will be merged into the real body tag in the DOM + +```jsx +import Nullstack from 'nullstack'; + +class Application extends Nullstack { + + // ... + + render() { + return ( + + {this.modalOpen && + +