Skip to content

Commit

Permalink
feat: completely replace redux with new plugin system
Browse files Browse the repository at this point in the history
  • Loading branch information
ephys committed Oct 1, 2018
1 parent 0dd2126 commit 1170ddf
Show file tree
Hide file tree
Showing 19 changed files with 111 additions and 69 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -13,6 +13,7 @@ node_modules
/public

# Auto-generated importable sub-modules
/i18n
/logger
/debug
/argv
31 changes: 8 additions & 23 deletions ROADMAP.md
@@ -1,5 +1,7 @@
# Roadmap

- extract most utils to @reworkjs/utils package

## Parallel Builds

- start: Auto-relaunch dead processes
Expand All @@ -12,8 +14,6 @@
- If no route matches, and no 404 is available, return a default 404 which prints the error message! (only in dev mode!)
- Generator Scripts:
- !! Add Route
- Add Locale
- eslint-loader should only be enabled if eslint is in the app's devDependencies or dependencies
- Don't create non-existing folders when building the app, simply ignore them. Create these folders during `rjs init`
- Generate a port from 3000 going up rather than generating a completely random port.
- Name more bundles
Expand All @@ -26,14 +26,10 @@

## Future versions

- `/app/resources` could be `/resources` ?
- Update React-Router
- Routes could be instances of classes which extend a default route class.
- This could expose a method which allows building the route using parameters.
- Server-side rendering
- Add a "no-js" class to body and remove it once the js has loaded
- Locales are only preloaded for the very first request, might need to call `import()` on them every time instead of caching as webpack already caches them.
- Extract decorators to their own package and rewrite them in a more modular way using visitor pattern.
- Extract redux to its own package
- Logger: Remove color codes when writing to file
- Add `rjs launch`
- Same as start but doesn't build.
Expand All @@ -44,9 +40,6 @@
- ServerBuilder:
- Should not output non-js assets (index.html, .css, /public). (file-loader: emitFile=false)
- Needs to know the reference to those assets.
- Intl:
- Load and activate the IntlPolyfill for the active locale
- http://blog.ksol.fr/user-locale-detection-browser-javascript/
- /favicon.ico should return the ico of the app.
- Better debug messages when file not found by webpack
```
Expand All @@ -57,27 +50,19 @@
@ ./~/reworkjs/lib/framework/client/index.js
@ multi main
```
- Documentation
- Getting Started
- How srcset-loader is used
- Don't use moment, use https://formatjs.io/|https://github.com/yahoo/react-intl.
- Unit Tests
- Documentation.
- Getting Started.
- How srcset-loader is used.
- Unit Tests.

## Potential future versions

- https://github.com/jhamlet/svg-react-loader for svg with query ?inline
https://github.com/jantimon/favicons-webpack-plugin
- https://github.com/webpack-contrib/json5-loader
- https://www.npmjs.com/package/postcss-image-set-polyfill
- Make `react-router@3.0.2`, `redux` dependencies instead of peerDeps ?
- @container could register used providers
- Autofix JS files ? https://github.com/okonet/lint-staged#automatically-fix-code-style-with---fix-and-add-to-commit
- Autofix css files ? https://github.com/okonet/lint-staged#automatically-fix-scss-style-with-stylefmt-and-add-to-commit
- Add support for https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache and manifest ?
- LoaderOptionsPlugin({ minimize: true })
- eslint plugin that detects @provider and warns if anything in the annotated class isn't static
- Replace current React-HMR system with https://github.com/gaearon/react-hot-loader ?
- @provider
- Getting non-defined state outside of reducers should throw
- Setting non-defined state outside of reducers should throw
- Replace current React-HMR system with https://github.com/gaearon/react-hot-loader
- FaviconsWebpackPlugin
3 changes: 0 additions & 3 deletions decorators.js

This file was deleted.

9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -102,6 +102,7 @@
"uglifyjs-webpack-plugin": "^1.2.4",
"universal-cookie-express": "^2.1.2",
"url-loader": "^1.0.1",
"val-loader": "^1.1.1",
"webpack": "^4.5.0",
"webpack-cleanup-plugin": "^0.5.1",
"webpack-dev-middleware": "^3.1.2",
Expand Down
1 change: 1 addition & 0 deletions src/exportables/i18n.js
@@ -0,0 +1 @@
export * from '../framework/common/i18n';
2 changes: 1 addition & 1 deletion src/framework/app/LanguageComponent.js
Expand Up @@ -25,7 +25,7 @@ type State = {
/**
* this component synchronizes the internal i18n state with react-intl.
*/
@withCookies()
@withCookies
export default class LanguageComponent extends React.Component<Props, State> {

state = {
Expand Down
5 changes: 0 additions & 5 deletions src/framework/client/client-hooks.js

This file was deleted.

15 changes: 15 additions & 0 deletions src/framework/client/client-hooks/_generate-hooks.js
@@ -0,0 +1,15 @@
// This file is run by webpack, and the code it generates is the one that will actually be used
// See: val-loader

/* eslint-disable import/no-commonjs */

// need to import from /lib (the version in which webpack is running) otherwise it will use /es
const { getHooks, HOOK_SIDES } = require('../../../../lib/internals/get-plugins');

module.exports = function getClientHooks() {
const hookFiles = getHooks(HOOK_SIDES.client);

const requireArray = `[${hookFiles.map(hookFile => `require(${JSON.stringify(hookFile)})`).join(',')}]`;

return { code: `export default ${requireArray};` };
};
1 change: 1 addition & 0 deletions src/framework/client/client-hooks/index.js
@@ -0,0 +1 @@
export { default } from 'val-loader!./_generate-hooks';
1 change: 0 additions & 1 deletion src/framework/common/i18n/_app-translations.js
Expand Up @@ -6,7 +6,6 @@ import { triggerHotReload } from './_hot-reload';
import { getFileName, getLocaleBestFit, runBundleLoader } from './_locale-utils';
import { getActiveLocale } from './index';

// TODO update these two when hot reload.
let messageTranslationsLoaders = loadMessageTranslationList();
let localeToFileMapping: Map<string, string> = buildMessagesLocaleList(messageTranslationsLoaders);

Expand Down
12 changes: 9 additions & 3 deletions src/framework/common/i18n/_native-intl.js
Expand Up @@ -3,16 +3,22 @@
import { hasNativeIntl } from '../intl-polyfil';
import { getFileName, getLocaleBestFit, runBundleLoader } from './_locale-utils';

// $FlowIgnore
const intlLocaleLoaders = require.context('bundle-loader?lazy&name=IntlLocale-[name]!intl/locale-data/jsonp', true, /\.js$/);
const availableIntlLocales: string[] = intlLocaleLoaders.keys().map(getFileName);
// TODO: memoize availableIntlLocales but only on server and in prod? It will be generated on every call.
function getIntlLocaleLoaders() {
// $FlowIgnore
return require.context('bundle-loader?lazy&name=IntlLocale-[name]!intl/locale-data/jsonp', true, /\.js$/);
}

export function installIntlLocale(localeName: string): Promise<void> {
return Promise.resolve().then(() => {
if (hasNativeIntl()) {
return null;
}

// Note: this should never be called on non-webpack generated builds
const intlLocaleLoaders = getIntlLocaleLoaders();
const availableIntlLocales: string[] = intlLocaleLoaders.keys().map(getFileName);

let actualLocale = getLocaleBestFit(localeName, availableIntlLocales);
if (actualLocale == null) {
console.error(`Could not fetch React-Intl locale ${localeName}, it does not exist (fallback to english).`);
Expand Down
1 change: 1 addition & 0 deletions src/framework/common/kernel.js
Expand Up @@ -6,6 +6,7 @@ import createRoutes from './create-routes';
import debug from './debug';

// useRouterHistory creates a composable higher-order function
// TODO createMemoryHistory: Should we create a new one for each Server side render?
const navigationHistory = process.env.SIDE === 'client' ? browserHistory : createMemoryHistory();

// Set up the router, wrapping all Routes in the App component
Expand Down
5 changes: 0 additions & 5 deletions src/framework/server/server-hooks.js

This file was deleted.

15 changes: 15 additions & 0 deletions src/framework/server/server-hooks/_generate-hooks.js
@@ -0,0 +1,15 @@
// This file is run by webpack, and the code it generates is the one that will actually be used
// See: val-loader

/* eslint-disable import/no-commonjs */

// need to import from /lib (the version in which webpack is running) otherwise it will use /es
const { getHooks, HOOK_SIDES } = require('../../../../lib/internals/get-plugins');

module.exports = function getServerHooks() {
const hookFiles = getHooks(HOOK_SIDES.server);

const requireArray = `[${hookFiles.map(hookFile => `require(${JSON.stringify(hookFile)})`).join(',')}]`;

return { code: `export default ${requireArray};` };
};
1 change: 1 addition & 0 deletions src/framework/server/server-hooks/index.js
@@ -0,0 +1 @@
export { default } from 'val-loader!./_generate-hooks';
26 changes: 20 additions & 6 deletions src/internals/get-plugins.js
Expand Up @@ -23,20 +23,34 @@ export default function getPlugins(): FrameworkPlugin[] {
}

const plugins = [];
for (const pluginConfig of pluginConfigs) {
const pluginUri = typeof pluginConfig === 'string' ? pluginConfig : pluginConfig.plugin;
const config = typeof pluginConfig === 'string' ? null : pluginConfig.config;
for (const [pluginModule, pluginConfig] of Object.entries(pluginConfigs)) {

// $FlowIgnore
const Plugin = require(pluginUri);
const pluginInstance = new Plugin(config);
let Plugin;
try {
// $FlowIgnore
Plugin = require(`${pluginModule}/plugin`);
} catch (e) {
console.error(`Could not find reworkjs plugin ${pluginModule}. Make sure the package is installed and module ${pluginModule}/plugin exists.`);
throw e;
}

if (Plugin.default) {
Plugin = Plugin.default;
}

const pluginInstance = new Plugin(pluginConfig);

plugins.push(pluginInstance);
}

return plugins;
}

export const HOOK_SIDES = Object.freeze({
client: 'client',
server: 'server',
});

export function getHooks(side: string): string[] {

const hooks = [];
Expand Down
48 changes: 27 additions & 21 deletions src/internals/webpack/WebpackBase.js
Expand Up @@ -16,7 +16,6 @@ import CopyWebpackPlugin from 'copy-webpack-plugin';
import frameworkConfig from '../../shared/framework-config';
import projectMetadata from '../../shared/project-metadata';
import frameworkMetadata from '../../shared/framework-metadata';
import { getHooks } from '../get-plugins';
import { resolveFrameworkSource } from '../util/resolve-util';
import argv from '../rjs-argv';
import logger from '../../shared/logger';
Expand Down Expand Up @@ -394,27 +393,21 @@ export default class WebpackBase {
const programArgv = minimist(argv['--'] || []);

const definedVariables: Object = {
'process.env.SIDE': SIDE,
'process.env.BUILD_ENV': NODE_ENV,
'process.env.PROCESS_NAME': JSON.stringify(`${projectMetadata.name} (${this.isServer() ? 'server' : 'client'})`),
process: {
env: {
SIDE,
NODE_ENV,
PROCESS_NAME: JSON.stringify(`${projectMetadata.name} (${this.isServer() ? 'server' : 'client'})`),
},
},
$$RJS_VARS$$: {
FRAMEWORK_METADATA: JSON.stringify(frameworkMetadata),
PROJECT_METADATA: JSON.stringify(projectMetadata),
PARSED_ARGV: JSON.stringify(programArgv),

HOOKS_CLIENT: buildRequireArrayScript(getHooks('client')),
HOOKS_SERVER: buildRequireArrayScript(getHooks('server')),
},
};

if (!this.isServer()) {
// define process on the browser
definedVariables.process = {
env: {
NODE_ENV,
},
};
} else {
if (this.isServer()) {
const outputDirectory = getWebpackSettings(this.isServer()).output.path;

// we only pass build & logs to bundled code
Expand Down Expand Up @@ -443,7 +436,7 @@ export default class WebpackBase {
definedVariables.$$RJS_VARS$$.FRAMEWORK_CONFIG = JSON.stringify(FRAMEWORK_CONFIG);
}

return definedVariables;
return flattenKeys(definedVariables);
}

/** @private */
Expand Down Expand Up @@ -533,12 +526,25 @@ function buildIndexPage() {
});
}

function buildRequireArrayScript(uris: string[]): string {
let script = 'r; var r = [];\n';
function buildRequireArrayScript(uris) {
const uriList = uris.map(uri => `require(${JSON.stringify(uri)})`).join(',');

return `[${uriList}]`;
}

function flattenKeys(obj, out = {}, paths = []) {

for (const key of Object.getOwnPropertyNames(obj)) {
paths.push(key);

if (typeof obj[key] === 'object') {
flattenKeys(obj[key], out, paths);
} else {
out[paths.join('.')] = obj[key];
}

for (const uri of uris) {
script += `r.push(require('${uri}'))`;
paths.pop();
}

return script;
return out;
}
2 changes: 1 addition & 1 deletion src/shared/framework-config/framework-config-type.js
Expand Up @@ -19,5 +19,5 @@ export type FrameworkConfigStruct = {
'pre-init': ?string,
'service-worker': ?string,

plugins: ?Array<FrameworkPluginConfig | string>,
plugins: ?{ [string]: any },
};

0 comments on commit 1170ddf

Please sign in to comment.