Open mrbut opened 5 years ago
Would additionally like to propose we add something like webpack's resolve modules: https://webpack.js.org/configuration/resolve/#resolve-modules.
ribbit.config.js
resolve_modules: ['node_modules', 'my_local_plugins', 'my_local_presets]
^__^ This way users can map to local paths instead of needing to worry about the need to cut a release for every single tiny change (aka we can test all the things or just use our own local private thing!)
The problem-1 What do we mean by switching to another server?
routing-output-ii how is this generated?
react-router are for this plugin, are we assuming they are also using redux?
Per my comments earlier, I'm requesting to revamp the plugin portion of this to match closer follow the two principles:
The plugins key in ribbit.config.js will control the order of composing the plugins and smart plugins will export an object that will be consumed.
Adding my board scribbles. Will write extended details:
Plugin config rewrite:
ribbit.config.js has a new plugins:
property. It can accept either an array or an object.
The array is a series of plugins that will be executed from right to left. The array can hold smart plugins and regular plugins.
Regular plugins export a function and they take care of a single task. The receive a single input as an argument and return a single output.
Smart plugins export an object that has preset set compositions of existing functions.
This approaches allows for users to have granular control over their plugins and also ensure that if there is a universal need for a set of functionality a smart plugin can remove the need to repeatedly write out the same plugin compositions.
Example with array and no smart plugins:
plugins: ['@ribbit/react-redux`, '@ribbit/react-router']
Plugins like dependencies will be symlinked. The genPhasePlugins
function will dynamically require all the modules in the plugins and check the exported object. It will generate an object that maps the plugin (#51) phase to the function it's trying to plug into.
Example with array and smart plugins:
plugins: ['@ribbit/react-redux-router`]
Plugin is exported as an object and treated as the pointer for the ribbit.config.js file's plugins property.
Example with object
plugins: {
execution: ['@ribbit/react-redux`, '@ribbit/react-router']
}
Plugin object can completely compose all phases plugins. User has complete control.
What?
This RFC is an outline for an approach to the core Ribbit application's plugin architecture.
The problem?
Ribbit in its base MVP state is coupled with a few technologies: Webpack for bundling and code splitting, React, React-Router, React-Redux.
This causes a few problems:
The solution?
1. Modularizing the application in the server
To modularize the application into its building blocks. (Note: each building block is a separate PR to ensure there is space to go into the details.)
We can map our application into six phases. The six phases to set up Universal JS are:
These building blocks— which are referenced as a "phase" or "phases" later in this document—can be used as a means of modularizing out the application from file structure to the actual process of executing the Universal Javascript set-up.
Folder Structure Refactor Proposal
The current server folder structure:
The proposed updated folder structure
Breaking up the functionality in the current helper files. Additionally removing the "helpers" folder and placing all js in it into the correct "phase" folder.
Structuring inputs and outputs for phases
Each phase acts as a precursor to its corresponding phase. It takes in input, and its output is consumed by a later phase.
ribbit init > routing > serializing > executing > rendering > response -> deserializing + attaching
Phase input and output approach
Routing - mapping components to routes
Input (from
ribbit init
)appRoot
path.routing
keyOutput
ribbit init
and additional input from a user fordependencies
(bundle related dependencies),plugins
(functionality that needs to plugin into the ribbit application), andwebpackSettings
(additional Webpack functionality required to render the application). Provided via the file system.{routes:[], routeAssetNameMap:[]}
an object contains the mappings for routes and names for staticSerializing
Input
const {store, window} from 'ribbit'
functions.serializing
keyOutput
Execution
Input
execution
keyOutput
<App /> becomes <StaticRouter context={} location={}><App /></StaticRouter>
Rendering
Input
Output
Response
Input
Output:
Deserializing + Attaching
Input
The methods produced for generating the outputs above can still be passed to our current middleware set-up. Organizing the app into methods makes it easier to pinpoint what each part of our application is doing and to locate the pieces of functionality that users are most likely to want to plugin to.
2. Decoupling opinionated frameworks from ribbit and injecting them as plugins
How does one plugin into the Ribbit architecture? This question breaks into two parts. The first, what functionality is running in each phase/how do we map functionality to a phase? The second, how do we plugin into that functionality from external plugins?
An approach to predictable functionality
Using library/framework presets as a means for creating predictable funcationality. Examples of preset would be: React or Vue. These presets would be saved as arrays with indexes containing default methods for a particular preset that will be run in chronological order within each phase of the build process. An example would be that in React generating JSX is required to generate the HTML string in the rendering phase. Thus for the JSX preset, we create a jsxCompose function, which will always run in the execution phase. All preset functions are required to take an array of plugins as their first argument. Additional inputs will be dictated by the data required to complete the function.
Example of a present: modules/presets/react.js
An approach to plugging external functionality into Ribbit
In each phase at the beginning, a function
getPhasePlugins
will be called. This function will look into the ribbit configs plugins property, which is an array of plugins.ribbit.config.js
getPhasePlugins
receives a single argument which is a the current phase as a string:getPhasePlugins('execution')
. The function will map through the plugins array in the ribbit.config.js file. Each (plugin)[https://github.com/team-bimmer/ribbit/issues/44] exports an object with keys that match each phase.getPhasePlugins
will loop through each project in the array and grab any method that is in an object with the same name as the current phase.The output of
getPhasePlugins
will be an object. Each key in the object maps to a function from the phase in the preset object. Each key's value is an array of all the methods that exist in the plugins under the current phrase name.The array will then be passed into the preset function as the first argument, and the function in the array will be composed together.
Example: If we use the configuration in ribbit.config.js above,
getPhasePlugins
will check in the exported objects from the packages. The packages would be in the user's node_modules folder./Users/someProject/node_modules/@ribbit/plugins/react-router
/Users/someProject/node_modules/@ribbit/plugins/react-redux
During the serializing phase (like every other phase before it) the
getPhasePlugins
will loop through the plugins array looking for a serialize key on their exported objects. It will then output the following object:When the
jsxCompose
function is run in serialize after the application bundling is complete the plugin methods array will be passed to is.jsxCompose(serializingPluginFns, <CompiledApp />)
The output will be a wrapped bundled App component produced by composing functions together.
That is the approach to injecting external plugins into the
ribbit build
process! 😃3. Abstract the server (creating a wrapper around Express)
As this is not MVP critical, this will be placed in a separate RFC written shortly.