Coder Social home page Coder Social logo

seznam / ima Goto Github PK

View Code? Open in Web Editor NEW
39.0 11.0 10.0 180.11 MB

Home Page: https://imajs.io

License: MIT License

JavaScript 29.42% HTML 0.12% CSS 0.45% Shell 0.23% Less 2.67% TypeScript 66.51% EJS 0.60%
ima isomorphic universal react node server-rendering imajs seznam browser javascript

ima's Introduction

IMA.js logo

IMA.js

Build Status dependencies Status Known Vulnerabilities code style: prettier

The IMA.js is an application development stack for developing isomorphic applications written in pure JavaScript and React.

Why we use IMA.js and you should too?

Here at Seznam.cz, development of a frontend application comes with many checkboxes that need to be ticked off before the project goes public. Mainly because of a diverse audience and a challenging product requirements.

In order to not reinvent the wheel on every project and to address all of these problems (checkboxes), we have created the IMA.js framework. Here are a few outlines that we're most proud of:

  • Isomorphic - application logic is first executed at the server-side, generates the page markup, and when the application logic is executed at the client-side it automatically binds to the server-generated markup and acts like a single-page application (or a multi-page application if the client does not support JavaScript). This allows for fast load times, out-of-the-box support for web crawlers and greater overall user experience (or UX for short).
  • React compatible - IMA.js Views extend the React Component and are in tight cooperation with our Controllers. That means you can use the full magic of React v16 without loosing anything.
  • Production ready - there's no need for additional setup or configuration. IMA.js uses environment-specific configurations from the start.
  • Battle tested - IMA.js is used on various projects across Seznam.cz. Some of them pushing the limits of what a frontend application can do.

Documentation

We have prepared a complex tutorial for you: Your first IMA.js application. This tutorial covers the basics of creating isomorphic web applications using IMA.js, but you will encounter some more advanced concepts in there as well.

For a more in-depth information about the IMA.js see a full documentation and more on imajs.io.

Main IMA.js parts

  • core - it contains base classes and common classes for every day work which allows you server side rendering and hydrating application in browser.
  • server - it contains methods which allow you to connect IMA application with the express framework.
  • gulp-tasks - IMA.js uses gulp for bundling and automatization. There are prepared common tasks and a base configuration.

Plugins

See the ima-plugins repository for available IMA.js plugins.

Getting Started

Initialize application skeleton with single command

npx create-ima-app
# or if you are using yarn
yarn create ima-app

and start the development!

cd create-ima-app
npm run dev
# or if you are using yarn
yarn dev

Contributing

See How to Contribute.

ima's People

Contributors

conymaniac avatar corvidism avatar crysadrak avatar dependabot[bot] avatar edar93 avatar famousgarkin avatar filipcima avatar filipoliko avatar greld avatar helesicovaa avatar ivo-urbanek avatar jenahajek avatar jsimck avatar jurca avatar kucharovic avatar lastuvka avatar lukaspan avatar matej-marcisovsky avatar mh--- avatar michalonderko avatar mjancarik avatar ondrejbase avatar premascz avatar rayus7 avatar svo404 avatar vojtasim avatar vottuscode avatar xurban42 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ima's Issues

ClientRouter.redirect bug

I found error on ClientRouter.redirect() function. When redirecting to url at other domain, but with url params containing current url redirect will fail.
This is caused by _isSameDomain check will match base url in url params.

Example:
redirecting from novinky.cz to login.szn.cz?return_url=novinky.cz will fail.

MetaManager Improvements

Right now there's now way to pass any additional data to the meta manager methods. This is useful for some specific meta tags, which require additional custom attributes.

It would be nice if all 3 current setters would accept 3rd additional optional argument, which would be an object consisting of key:value attribute names and values.

metaManager.setMetaName('image', '/img.source.jpg', { 'data-size': 'large', id: 'custom-meta-tag-id' });
metaManager.setMetaProperty('image:property', 'value', { 'data-size': 'large', id: 'custom-meta-tag-id' });
metaManager.setLink('license', 'MIT', { 'data-license-src': '...' });

The corresponding getters would then return object as a value:

metaManager.getMetaName('image'); // -> { content:  '/img.source.jpg', 'data-size': 'large', id: 'custom-meta-tag-id' }
metaManager.getMetaProperty('image:property'); // -> { content:  'value', 'data-size': 'large', id: 'custom-meta-tag-id' }
metaManager.getLink('license'); // -> { rel:  'MIT', 'data-license-src': '...' }

The existing usage with only 2 values (name and value) would have the same functionality as before.

Additional changes

Currently the meta manager only updates already existing meta tags. This means that you need to explicitly render those tags (even if they are empty and used in only few views) in DocumentView and spa.html, otherwise these are not rendered and skipped. Also when any view does not define these explicitly rendered meta tags, they are updated with empty content/rel values which is not perfect.

I propose some changes to the current meta handling:

  • Controller's setMetaParams should be the single source of truth for meta parameters on current route
  • The meta tags should be insterted/replaced/removed when the route change so they match exactly the ones defined for current view, while removing empty tags and inserting new ones. This would eliminate empty meta tags which are currently present for controllers that don't define value for them
  • ^^ would resolve in no need to explicitly define meta tags in the spa.html completely and DocumentView could benefit from simpler approach, since you don't need to pre-define all possible meta tags that are used across the application, but only the ones for current route, since the application will insert missing tags in the SPA mode automatically:
{metaManager.getMetaProperties().map(name => (
  <meta key={name} content={metaManager.getMetaProperty(name)} property={name} />
))}

{metaManager.getLinks().map(rel => (
  <link key={rel} href={metaManager.getLink(rel)} rel={rel} />
))}

Deep merge localization

  • Allow deep merging of localization files
  • Currently it only merges configs per-file
  • This would allow easier localization overrides from ima plugins

Do redirect in debug mode

If I use router.redirect with debug mode set to true I get error, but page won't redirect to new url. It would be great to redirect even with debug mode on.

Configurable open url

When ima dev server start, it open application on localhost on browser. It would be nice to have customizable open url. Our team use aliases for local development so we would appreciate this option.

Add support for `next` callback in route middlewares.

Similarly to the express, the route middlewares would work differently, where by default you will be stuck on it's execution, until the next callback is called.

This allows for more control inside the middlewares and enables us to create redirect middlewares and other features which should not result in the rendering of the page itself.

Multi-level immersion in Dictionary class

Multi-level immersion in Dictionary class

/packages/core/src/dictionary/MessageFormatDictionary.js

following method :

  has(key) {
    if (!/^[^.]+\.[^.]+$/.test(key)) {
      throw new Error(
        `The provided key (${key}) is not a valid localization ` +
          `phrase key, expecting a "file_name.identifier" notation`
      );
    }

    return !!this._getScope(key);
  }

does not allow multi-level immersion

eg. dictionary:

{
"banReasonTypes":{
        "OFFENCE": "Vulgarities and insults",
        "CRIME": "Approval of a crime, denial or justification of genocide",
        "TROLLING": "Trolling",
        "QUESTIONABLE_SITE": "Website with questionable content, dissemination of misinformation",
}
}
this.localize(filename.banReasonTypes.OFFENCE)

returns "Vulgarities and insults", but

this.utils.$Dictionary.has(filename.banReasonTypes.OFFENCE)

results in thrown error due to regex check in body of the has(key) method even though private method _getScope used in has(key) works fine with key filename.banReasonTypes.OFFENCE

i reccomend to change regex /^[^.]+\.[^.]+$/ to /^[^.]+(\.[^.]+)+$/

Missing ErrorOverlay when there's error in import path

When an error occurs after defining wrong input path (for example import Card from 'app/component/card/Cardasdfasdfasdf'; in HomeView of create-ima-app template). An error occurs, which is correctly reported in the CLI, however web app doesn't show any ErrorOverlay, even thou the SSE reports a compile error.

Improvements to Extensions definition API inside Controllers

Current state

Right now if I want to link extension to concrete page Controller, I need to get an instance of it (usually through $dependencies -> constructor -> instance variable) and call addExtension in init method.

import { AbstractController } from '@ima/core';
import GalleryExtension from 'app/component/gallery/GalleryExtension';

export default class PostController extends AbstractController {
  static get $dependencies() {
    return [GalleryExtension];
  }

  constructor(galleryExtension) {
    this._galleryExtension = galleryExtension;
  }

  init() {
    this.addExtension(this._galleryExtension);
  }
}

This brings a generous amount of boilerplate while currently there's no easy way to define a set of extensions which should be used on multiple controllers, without their explicit definition or some other helper that calls the addExtension behind the scenes.

API Extension proposals

I propose additional 2 methods that can be used along with the original API. These try to minimize the boilerplate needed, focus on better readability and reusability.

1. $extensions

The first addition is in a form of new $extensions static getter in the Controllers. This would work the same way as the current $dependencies (e.g. the instances are handled by the ObjectContainer), while eliminating the usually not-needed boilerplate.

import { AbstractController } from '@ima/core';
import GalleryExtension from 'app/component/gallery/GalleryExtension';

export default class PostController extends AbstractController {
  static get $extensions() {
    return [GalleryExtension];
  }
}

this would also simplify the definition of global extension stacks:

import { AbstractController } from '@ima/core';
import { GlobalExtensions } from 'app/config/extensions';
import GalleryExtension from 'app/component/gallery/GalleryExtension';

export default class PostController extends AbstractController {
  static get $extensions() {
    return [...GlobalExtensions, GalleryExtension];
  }
}

2. Extensions defined in the router itself

The Extensions definition would take place while defining the route along with it's view and controller:

import HomeController from 'app/page/home/HomeController';
import HomeView from 'app/page/home/HomeView';
import GalleryExtension from 'app/component/gallery/GalleryExtension';

export let init = (ns, oc, config) => {
  const router = oc.get('$Router');

  router
    .add('home', '/', HomeController, HomeView, {
      extensions: [GalleryExtension]
    })
}

The same benefits as with the first solution apply here.

Both API extensions should work along with the current implementation (unless there's no way to leave them both in the code).

dummy app require en language

We need to have configured en language in ima.config.js . Without this settings app crashes.

e.g.

module.exports = {
  languages: {
    en: [
      ...
    ]
  }
}

Optional and spread dependencies in ObjectContainer

Right now, there is no way to make any dependency in the $dependencies field optional. I propose adding a way of flagging dependencies as optional. These dependencies would then return undefined if they are not present in the OC rather than failing to initialize.

Syntax

import TextHelper from './TextHelper';

class TestHelper {
  static get $dependencies() {
    return [
      '?OptionalAliasedEntity',
      [TextHelper, { optional: true }]
    ]
  }
}

Additionally we could also add support for spreading array-like aliased dependencies:

import TextHelper from './TextHelper';

class TestHelper {
  static get $dependencies() {
    return [
      '...ArrayDependencies', // ArrayDependencies === [Dep1, Dep2, Dep3]
    ]
  }
}

This would add the ability to spread dependencies that are also defined as aliases.

Documentation enhancements

Below is a list of things that are currently missing from the documentation. It would be nice to have at least some information about following topics in the future:

Current version

  • Apart from the generated documentation, there is no info on how to use the HttpAgent. It would be nice to write up small article on how to use the HttpAgent to fetch data, since it is usually the first thing you want to do in any web application.

Next

  • No info about @ima/cli -> small summary of available commands and their arguments should be sufficient.
  • Describe new webpack-related features (direct asset imports in code, built-time runner generation, ima.config.js, customization options, CLI plugins and config override options, glob imports in LESS, experiments)
  • Static assets definition in environment.js (JS & CSS) and how to inject them into DocumentView/SPA.html

This list will probably be updated overtime as new features are implemented or existing issues are already resolved.

Extensions don't receive full state on update.

Scenario:

  • Route uses { onlyUpdate: true } option
  • Controller sets service, article and settings properties to state in the load() method.
  • During update it sets only article property to the state as anything else is unnecessary to change.
  • update() method of each initialised extension receives only partial state thus only { article }. It should receive the full state extended by the partial state.

Missing optional parameters evaluated as 'undefined' (string)

Optional parameters in static routes are evaluated as 'undefined' (string) instead undefined

Steps to reproduce: In create-ima-app add new page, .add('test', '/test/:?param1/:?param2', TestController, TestView) to routes.js

app/page/test/TestController.js :

import AbstractPageController from 'app/page/AbstractPageController';

export default class TestController extends AbstractPageController {
  static get $dependencies() {
    return [];
  }

  constructor() {
    super();
  }

  load() {
    const {param1, param2} = this.params;
    return {
      param1, param2
    };
  }
}

app/page/test/TestView.js :


export default function TestView({param1, param2}) {

  console.log('params:', param1, param2)

  console.log('param2 is undefined? ', param2 === undefined)
  console.log('param2 is \'undefined\'?', param2 === 'undefined')
  return (
    <div className='page-test'>
      <h1>Test page</h1>
      <p><pre>{typeof param1}</pre></p>
      <p><pre>{typeof param2}</pre></p>
    </div>
  );
}

go to http://localhost:3001/test/xxx - param2 is evaluated as 'undefined'

npx create-ima-app imajs-tutorial error

`➜ npx create-ima-app imajs-tutorial
Need to install the following packages:
create-ima-app
Ok to proceed? (y)
? Choose a template for your new IMA.js application: Empty - The basic Hello World example. Ideal for new projects.

info: Creating new IMA.js app inside imajs-tutorial directory...
info: Creating basic directory structure...
info: Running npm install inside app directory, this might take a while...
Press CTRL+C to cancel.

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! Found: [email protected]
npm ERR! node_modules/react
npm ERR! react@"16.12.0" from the root project
npm ERR! peer react@"16.x" from @ima/[email protected]
npm ERR! node_modules/@ima/core
npm ERR! @ima/core@"17.10.0" from the root project
npm ERR! peer @ima/core@"17.x" from @ima/[email protected]
npm ERR! node_modules/@ima/plugin-hot-reload
npm ERR! @ima/plugin-hot-reload@"0.1.0" from the root project
npm ERR! 3 more (react-dom, enzyme-adapter-react-16, enzyme-adapter-utils)
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.14.0" from [email protected]
npm ERR! node_modules/enzyme-adapter-react-16/node_modules/react-test-renderer
npm ERR! react-test-renderer@"^16.0.0-0" from [email protected]
npm ERR! node_modules/enzyme-adapter-react-16
npm ERR! dev enzyme-adapter-react-16@"1.15.1" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR!
npm ERR! See /Users/max/.npm/eresolve-report.txt for a full report.

npm ERR! A complete log of this run can be found in:
npm ERR! /Users/max/.npm/_logs/2021-04-27T09_42_51_039Z-debug.log
(node:41781) UnhandledPromiseRejectionWarning: Error: Command failed with exit code 1: npm install
at makeError (/Users/max/.npm/_npx/5c90cf034c3aec33/node_modules/execa/lib/error.js:56:11)
at Function.module.exports.sync (/Users/max/.npm/_npx/5c90cf034c3aec33/node_modules/execa/index.js:188:17)
at createImaApp (/Users/max/.npm/_npx/5c90cf034c3aec33/node_modules/create-ima-app/scripts/create.js:104:9)
at /Users/max/.npm/_npx/5c90cf034c3aec33/node_modules/create-ima-app/scripts/create.js:44:3
at processTicksAndRejections (internal/process/task_queues.js:93:5)
(Use node --trace-warnings ... to show where the warning was created)
(node:41781) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag --unhandled-rejections=strict (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
`

So I cd in the directory and npm install --force and seems to work

Chunks in non es build have wrong content hash

Requests for chunks returns 410 http code. This happens only in production build, not in development build.

temp fix:

webpack: async(config, ctx) => {
		if (ctx.command === 'build') {
			config.output = {
				...config.output,
				chunkFilename: () => `${ctx.outputFolders.js}/chunk.[id].${packageJson.version}.js`
			};
		}

		return config;
	},

`server` bundle output in @ima/cli is sometimes empty

Sometimes stats produced at the end of npx ima build|dev commands have empty stats for server bundle:

(not sure how to easily reproduce it)

image

EDIT: Looks like this happens when server does not manage to built in time...

ReactDOM.unmountComponentAtNode does not support callback

unmount() {
if (this._reactiveView) {
this._ReactDOM.unmountComponentAtNode(this._viewContainer, () => {
this._dispatcher.fire(Events.UNMOUNTED, { type: Types.UNMOUNT }, true);
});
this._reactiveView = null;
}
}

The unmount event is never called, since ReactDOM.unmountComponentAtNode does not take second argument (https://reactjs.org/docs/react-dom.html#unmountcomponentatnode, https://github.com/facebook/react/blob/60016c448bb7d19fc989acd05dda5aca2e124381/packages/react-dom/src/client/ReactDOMLegacy.js#L340). Does this cause any bugs, or can we just remove it?

Add ability to import `oc` using esm imports.

Not sure now if it's possible... but it would be cool if we would have access to oc through imports, e.g. it would be a singleton.

Since IMA is instance based, everything useful is located inside the oc, which then has to be somehow passed through the application (Context in react). For example in react-hooks, every component uses this context to extract usefull hooks. This is not very efficient, since context update triggers updates across almost every component.

Having ability to access these OC instances outside of the context, would add much more flexibility and performance improvements.

Better error reporting

Since ErrorOverlay now also displays GenericError.params when available in additional JSONView, we would probably benefit in replacing certain Errors in core with GenericError and additional params object for easier debugging (mainly ObjectContainer etc.)

Add option to run dev server on https

We use https even on dev enviroment to allow login authorization. It would be nice to add to ima configuration option to make it run on https. Certificate route might be part of configuration to make it general.

IMA@18

This issue tracks progress towards IMA@18 release.

Missing features

  • Add options to disable build of legacy version from ima.config.js
  • Migrate to new version of message format #206

Known issues

  • Currently dynamic imports don't work on server-side. (works)
  • When error-overlay displays compile error and the browser window is refreshed, there's blank page, instead of SSR rendered error overlay for compile error, as it's done with the runtime SSR error overlay (not sure if this can be done easily). (Fixed in #210)
  • README and documentation missing for new packages (@ima/cli, @ima/dev-utils, @ima/error-overlay, @ima/hmr-client).
  • extractSourceMappingUrl in @ima/dev-utils can probably be simplified. #218 (works as intented, replaces source-map with source-map-js pkg
  • node 18 support (drop node-fetch dependency in favor of native implementation of fetch in node 18). (Fixed in #210)
  • Kill already running applications on the same port when running npm run dev (e.g. already running app on 3001 is closed before starting new one, so npm run dev never fails in these situations). #213
  • Drop node-fetch, use native fetch from node 18 by default. #212

Potential improvements (don't need to be ready for IMA@18 release)

  • Refactor @ima/helpers to esm. #214
  • #261
  • Define exports fields in package.json on @ima/dev-utils to have nicer imports along with Typescript 4.7 update (when released)
  • Auto generate $Runner assets ($Source) at build-time (GenerateRunnerPlugin extension)
    • Content hashes can be used in this case for cache busing, instead of the ?v=[version] param (this results in faster load times in dev)
    • We would no longer need to define $Source asset paths in environment.js, however we still need to have an option to define custom attributes or conditional renders for each asset.
  • Try to get webpack-dev-middleware to work with in-memory assets only. Currently every HMR update is written and served from disk. webpack-dev-middleware has an option to serve these updates directly from memory using memfs. (may have something to do with webpack/webpack#15206)

TODO

core

  • globals from setupFile are not visible to TS compiler (using, extend), some tests are commented out
  • RouteOptions type is defined in core but is using ComponentType from react - core should not have react dependency

react-page-renderer

  • some tests with jest fake timers do not work (timeout) and are commented out
  • jest.config.js needs polishing (copy config from core)
  • types from core has to be removed in favor of new defition files from core (after core is in TS)
  • ClientPageRenderer and LegacyClientPageRenderer has to be removed from index.ts and compiled separately

This issue will be updated as new fixes/features are merged to next or new issues are discovered.

Migrate from message format v2 to v3

The [email protected] package we're currently using is deprecated and was replaced by v3.

However apart from some changes to the API, the v3 generates compiled messages in es6+ JS code with arrow functions, this blocks us from upgrading until we drop the support for es5 builds in the IMA.js for future version.

In case the use of deprecated package would become an issue an alternative has to be found. For now I am leaving the issue here just for the safekeeping.

TypeScript - Types tracking issue

This issue represents TODO list which will be updated periodically, with tasks that should be fixed in order to improve ima TS typing

  • Improve object container typing
  • Fix Controller and extension types across the board (sometimes the types expect instance but are typed with constructor and the other way around)
  • Add State generic definition to Page controller (similar to how react class components are done)
  • Add Generics to page state manager

Add support to HttpAgent for cancelable requests using AbortController

To fully support AbortController we have to make sure these points are completed

1) The HttpAgent works with abort signal passed into the fetch options and can be canceled. There are no issues with request caching etc.
2) The user can provide custom abort signal through fetchOptions:

const controller = new AbortController();
const { signal } = controller;

httpAgent.get('url', null, { fetchOptions: signal });

3) The user can provide abort controller instance in httpAgent options, which can be also used to abort timed out requests (if timeout is enabled):

const controller = new AbortController();

httpAgent.get('url', null, { controller });

4) If there's timeout option on the http agent, and user does not provide custom abort controller, we want to create custom instance directly in the http agent and use it to abort timeout requests.

In both 3. and 4. points where we reuse the abort controller to abort timeout, we need to make sure that the fetch still returns the TIMEOUT GenericError type. This means that we need to somehow catch the abort reject error and process it to the original generic one.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.