diff --git a/package-lock.json b/package-lock.json index d8035da00..251c84d7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6073,7 +6073,7 @@ "process": "^0.11.9", "punycode": "^1.4.1", "querystring-es3": "^0.2.1", - "readable-stream": "git+https://github.com/meteor/readable-stream.git#2e9112d7d31a2af6e0682db0e18679b1e5fd4694", + "readable-stream": "git+https://github.com/meteor/readable-stream.git#c688cdd193549919b840e8d72a86682d91961e12", "stream-browserify": "^2.0.1", "string_decoder": "^1.0.1", "timers-browserify": "^1.4.2", @@ -6520,7 +6520,7 @@ "integrity": "sha1-Z0yZdgkBw8QRJ3GjHlIdw0nMCew=" }, "readable-stream": { - "version": "git+https://github.com/meteor/readable-stream.git#2e9112d7d31a2af6e0682db0e18679b1e5fd4694", + "version": "git+https://github.com/meteor/readable-stream.git#c688cdd193549919b840e8d72a86682d91961e12", "from": "git+https://github.com/meteor/readable-stream.git", "requires": { "inherits": "~2.0.1", diff --git a/packages/vulcan-core/lib/client/components/AppContainer.jsx b/packages/vulcan-core/lib/client/components/AppContainer.jsx deleted file mode 100644 index 74e541433..000000000 --- a/packages/vulcan-core/lib/client/components/AppContainer.jsx +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Client specific wrapper, such as the BrowserRouter - */ -import { - registerComponent, -} from 'meteor/vulcan:lib'; -import React, { PureComponent } from 'react'; -//import PropTypes from 'prop-types'; -import { BrowserRouter } from 'react-router-dom' - -class AppContainer extends PureComponent { - render() { - const { children } = this.props - return ( - - {children} - - ) - } -} - -AppContainer.displayName = 'AppContainer'; - -registerComponent({ name: 'AppContainer', component: AppContainer }) - -export default AppContainer; diff --git a/packages/vulcan-core/lib/client/components/AppGenerator.jsx b/packages/vulcan-core/lib/client/components/AppGenerator.jsx new file mode 100644 index 000000000..2ab13aa33 --- /dev/null +++ b/packages/vulcan-core/lib/client/components/AppGenerator.jsx @@ -0,0 +1,30 @@ +/** + * The App + relevant wrappers + */ +import React from 'react'; +import { ApolloProvider } from 'react-apollo'; +import { runCallbacks } from '../../modules' + +import { Components } from 'meteor/vulcan:lib' +import { CookiesProvider } from 'react-cookie'; +import { BrowserRouter } from 'react-router-dom' + +const AppGenerator = ({ apolloClient }) => { + const App = ( + + + + + + + + ); + // run user registered callbacks to wrap the app + const WrappedApp = runCallbacks({ + name: 'router.client.wrapper', + iterator: App, + properties: { apolloClient } + }); + return WrappedApp; +}; +export default AppGenerator; \ No newline at end of file diff --git a/packages/vulcan-core/lib/client/main.js b/packages/vulcan-core/lib/client/main.js index fd990a7bf..bdd7a2713 100644 --- a/packages/vulcan-core/lib/client/main.js +++ b/packages/vulcan-core/lib/client/main.js @@ -1,4 +1,3 @@ -import './components/AppContainer' export * from '../modules/index.js'; export * from './start.jsx'; \ No newline at end of file diff --git a/packages/vulcan-core/lib/client/start.jsx b/packages/vulcan-core/lib/client/start.jsx index 5a072d837..c30d30b8a 100644 --- a/packages/vulcan-core/lib/client/start.jsx +++ b/packages/vulcan-core/lib/client/start.jsx @@ -1,12 +1,10 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { onPageLoad } from 'meteor/server-render'; -import { ApolloProvider } from 'react-apollo'; -import { CookiesProvider } from 'react-cookie'; +import AppGenerator from './components/AppGenerator' import { createApolloClient, - Components, populateComponentsApp, populateRoutesApp, initializeFragments @@ -25,11 +23,7 @@ Meteor.startup(() => { document.body.appendChild(rootElement); const Main = () => ( - - - - - + ); onPageLoad(() => { diff --git a/packages/vulcan-core/lib/modules/components/App.jsx b/packages/vulcan-core/lib/modules/components/App.jsx index 81038e5c2..b8cc1df3d 100644 --- a/packages/vulcan-core/lib/modules/components/App.jsx +++ b/packages/vulcan-core/lib/modules/components/App.jsx @@ -107,29 +107,27 @@ class App extends PureComponent { render() { const routeNames = Object.keys(Routes); return ( - - -
- - {/* */} - {this.props.currentUserLoading ? ( - - ) : routeNames.length ? ( - - {routeNames.map(key => ( - // NOTE: if we want the exact props to be taken into account - // we have to pass it to the RouteWithLayout, not the underlying Route, - // because it is the direct child of Switch - - ))} - // TODO Apollo2: figure out why this is not working - - ) : ( - - )} -
-
-
+ +
+ + {/* */} + {this.props.currentUserLoading ? ( + + ) : routeNames.length ? ( + + {routeNames.map(key => ( + // NOTE: if we want the exact props to be taken into account + // we have to pass it to the RouteWithLayout, not the underlying Route, + // because it is the direct child of Switch + + ))} + // TODO Apollo2: figure out why this is not working + + ) : ( + + )} +
+
); } } diff --git a/packages/vulcan-core/lib/server/components/AppContainer.jsx b/packages/vulcan-core/lib/server/components/AppContainer.jsx deleted file mode 100644 index f4f077345..000000000 --- a/packages/vulcan-core/lib/server/components/AppContainer.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import { - registerComponent, -} from 'meteor/vulcan:lib'; -import React, { PureComponent } from 'react'; -//import PropTypes from 'prop-types'; - -class AppContainer extends PureComponent { - render() { - const { children } = this.props - return ( - children - ) - } -} - -AppContainer.displayName = 'AppContainer'; - -registerComponent({ name: 'AppContainer', component: AppContainer }) - -export default AppContainer; diff --git a/packages/vulcan-core/lib/server/main.js b/packages/vulcan-core/lib/server/main.js index 015b8b72b..6220f9d68 100644 --- a/packages/vulcan-core/lib/server/main.js +++ b/packages/vulcan-core/lib/server/main.js @@ -1,4 +1,3 @@ -import './components/AppContainer'; import './start.js'; export * from '../modules/index.js'; diff --git a/packages/vulcan-lib/lib/client/main.js b/packages/vulcan-lib/lib/client/main.js index ba77d97a2..5536104df 100644 --- a/packages/vulcan-lib/lib/client/main.js +++ b/packages/vulcan-lib/lib/client/main.js @@ -1,7 +1,6 @@ import './auth.js'; export * from '../modules/index.js'; -export * from './mongo_redux.js'; export * from './render_context.js'; export * from './inject_data.js'; diff --git a/packages/vulcan-lib/lib/client/mongo_redux.js b/packages/vulcan-lib/lib/client/mongo_redux.js deleted file mode 100644 index 5efde6c33..000000000 --- a/packages/vulcan-lib/lib/client/mongo_redux.js +++ /dev/null @@ -1,12 +0,0 @@ -// import { getRenderContext } from './render_context.js'; - -// const { store } = getRenderContext; - -// // use global store -// Mongo.Collection.prototype.findRedux = function (selector = {}, options = {}) { -// return this.findInStore(store, selector, options); -// } - -// Mongo.Collection.prototype.findOneRedux = function (_idOrObject) { -// return this.findOneInStore(store, _idOrObject); -// } diff --git a/packages/vulcan-lib/lib/modules/index.js b/packages/vulcan-lib/lib/modules/index.js index dab204e25..81a28bab7 100644 --- a/packages/vulcan-lib/lib/modules/index.js +++ b/packages/vulcan-lib/lib/modules/index.js @@ -7,7 +7,6 @@ import './deep_extend.js'; // import './intl_polyfill.js'; // import './graphql.js'; import './icons.js'; -import './mongo_redux.js'; export * from './components.js'; export * from './collections.js'; @@ -17,7 +16,6 @@ export * from './routes.js'; export * from './utils.js'; export * from './settings.js'; export * from './strings.js'; -export * from './redux.js'; export * from './headtags.js'; export * from './fragments.js'; export * from './apollo-common' diff --git a/packages/vulcan-lib/lib/modules/redux.js b/packages/vulcan-lib/lib/modules/redux.js deleted file mode 100644 index eb24a58c7..000000000 --- a/packages/vulcan-lib/lib/modules/redux.js +++ /dev/null @@ -1,79 +0,0 @@ -// import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; - -// // create store, and implement reload function -// export const configureStore = (reducers, initialState = {}, middlewares) => { -// let getReducers; -// if (typeof reducers === 'function') { -// getReducers = reducers; -// reducers = getReducers(); -// } -// reducers = typeof reducers === 'object' ? combineReducers(reducers) : reducers; -// middlewares = Array.isArray(middlewares) ? middlewares : [middlewares]; - -// const store = createStore( -// // reducers -// reducers, -// // initial state -// initialState, -// // middlewares -// compose( -// applyMiddleware(...middlewares), -// typeof window !== 'undefined' && window.devToolsExtension ? window.devToolsExtension() : f => f, -// ), -// ); - -// store.reload = function reload(options = {}) { -// if (typeof options.reducers === 'function') { -// getReducers = options.reducers; -// options.reducers = undefined; -// } -// if (!options.reducers && getReducers) { -// options.reducers = getReducers(); -// } -// if (options.reducers) { -// reducers = typeof options.reducers === 'object' ? combineReducers(options.reducers) : options.reducers; -// } -// this.replaceReducer(reducers); -// return store; -// }; - -// return store; -// }; - -// // action -// // **Notes: client side, addAction to browser** -// // **Notes: server side, addAction to server share with every req** -// let actions = {}; - -// export const addAction = (addedAction) => { -// actions = { ...actions, ...addedAction }; -// return actions; -// }; -// export const getActions = () => actions; - -// // reducers -// // **Notes: client side, addReducer to browser** -// // **Notes: server side, addReducer to server share with every req** -// let reducers = {}; - -// export const addReducer = (addedReducer) => { -// reducers = { ...reducers, ...addedReducer }; -// return reducers; -// }; -// export const getReducers = () => reducers; - -// // middlewares -// // **Notes: client side, addMiddleware to browser** -// // **Notes: server side, addMiddleware to server share with every req** -// let middlewares = []; - -// export const addMiddleware = (middlewareOrMiddlewareArray, options = {}) => { -// const addedMiddleware = Array.isArray(middlewareOrMiddlewareArray) ? middlewareOrMiddlewareArray : [middlewareOrMiddlewareArray]; -// if (options.unshift) { -// middlewares = [...addedMiddleware, ...middlewares]; -// } else { -// middlewares = [...middlewares, ...addedMiddleware]; -// } -// return middlewares; -// }; -// export const getMiddlewares = () => middlewares; diff --git a/packages/vulcan-lib/lib/server/apollo-ssr/components/AppGenerator.jsx b/packages/vulcan-lib/lib/server/apollo-ssr/components/AppGenerator.jsx index a4a08e2e5..ef94bae71 100644 --- a/packages/vulcan-lib/lib/server/apollo-ssr/components/AppGenerator.jsx +++ b/packages/vulcan-lib/lib/server/apollo-ssr/components/AppGenerator.jsx @@ -5,23 +5,20 @@ import React from 'react'; import { ApolloProvider } from 'react-apollo'; import { StaticRouter } from 'react-router'; -// TODO: -// Problem: Components is only created on Startup -// so Components.App is not defined here import { Components } from 'meteor/vulcan:lib' import { CookiesProvider } from 'react-cookie'; - import Cookies from 'universal-cookie'; +import Cookies from 'universal-cookie'; // The client-side App will instead use // see client-side vulcan:core/lib/client/start.jsx implementation // we do the same server side -const AppGenerator = ({ req, client, context }) => { +const AppGenerator = ({ req, apolloClient, context }) => { // TODO: universalCookies should be defined here, but it isn't // @see https://github.com/meteor/meteor-feature-requests/issues/174#issuecomment-441047495 const cookies = new Cookies(req.cookies) // req.universalCookies; const App = ( - + @@ -29,6 +26,6 @@ const AppGenerator = ({ req, client, context }) => { ); - return App; + return App }; export default AppGenerator; diff --git a/packages/vulcan-lib/lib/server/apollo-ssr/renderPage.js b/packages/vulcan-lib/lib/server/apollo-ssr/renderPage.js index ca3380baa..8b69ccd00 100644 --- a/packages/vulcan-lib/lib/server/apollo-ssr/renderPage.js +++ b/packages/vulcan-lib/lib/server/apollo-ssr/renderPage.js @@ -8,6 +8,7 @@ import React from 'react'; import ReactDOM from 'react-dom/server'; import { renderToStringWithData } from 'react-apollo'; +import { runCallbacks } from '../../modules/callbacks' import { createClient } from './apolloClient'; import Head from './components/Head' @@ -22,7 +23,8 @@ const makePageRenderer = ({ computeContext }) => { // this avoids caching server side const client = await createClient({req, computeContext}); - // TODO? do we need this? + // Used by callbacks to handle side effects + // E.g storing the stylesheet generated by styled-components const context = {}; @@ -30,15 +32,22 @@ const makePageRenderer = ({ computeContext }) => { // middlewares at this point // @see https://github.com/meteor/meteor-feature-requests/issues/174#issuecomment-441047495 + const App = + + // run user registered callbacks that wraps the React app + const WrappedApp = runCallbacks({ + name: 'router.server.wrapper', + iterator: App, + properties: { req, context, apolloClient: client } + }); + // equivalent to calling getDataFromTree and then renderToStringWithData - const content = await renderToStringWithData( - - ) + const htmlContent = await renderToStringWithData(WrappedApp) // TODO: there should be a cleaner way to set this wrapper // id must always match the client side start.jsx file - const wrappedContent = `
${content}
` - sink.appendToBody(wrappedContent) + const wrappedHtmlContent = `
${htmlContent}
` + sink.appendToBody(wrappedHtmlContent) // TODO: this sounds cleaner but where do we add the
? //sink.renderIntoElementById('react-app', content) @@ -52,6 +61,13 @@ const makePageRenderer = ({ computeContext }) => { ) sink.appendToBody(serializedApolloState) + + // post render callback + runCallbacks({ + name: 'router.server.postRender', + iterator: sink, + properties: { context } + }); } return renderPage } diff --git a/packages/vulcan-redux/lib/client/main.js b/packages/vulcan-redux/lib/client/main.js new file mode 100644 index 000000000..b1216bb37 --- /dev/null +++ b/packages/vulcan-redux/lib/client/main.js @@ -0,0 +1,4 @@ +import setupRedux from './setupRedux'; +setupRedux(); + +export * from '../modules/index'; diff --git a/packages/vulcan-redux/lib/client/setupRedux.js b/packages/vulcan-redux/lib/client/setupRedux.js new file mode 100644 index 000000000..9bba72013 --- /dev/null +++ b/packages/vulcan-redux/lib/client/setupRedux.js @@ -0,0 +1,13 @@ +import React from 'react' +import { Provider } from 'react-redux' +import { addCallback } from 'meteor/vulcan:core' +import { initStore } from '../modules/store' + +const setupRedux = () => { + const store = initStore() + addCallback('router.client.wrapper', + function ReduxStoreProvider(app) { + return {app} + }); +} +export default setupRedux \ No newline at end of file diff --git a/packages/vulcan-redux/lib/modules/index.js b/packages/vulcan-redux/lib/modules/index.js new file mode 100644 index 000000000..efc4a60c0 --- /dev/null +++ b/packages/vulcan-redux/lib/modules/index.js @@ -0,0 +1 @@ +export * from './redux.js'; \ No newline at end of file diff --git a/packages/vulcan-redux/lib/modules/redux.js b/packages/vulcan-redux/lib/modules/redux.js new file mode 100644 index 000000000..078dc8266 --- /dev/null +++ b/packages/vulcan-redux/lib/modules/redux.js @@ -0,0 +1,96 @@ +import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; +import _isEmpty from 'lodash/isEmpty' +// TODO: now we should add some callback call to add the store to +// Apollo SSR + client side too + +// create store, and implement reload function +export const configureStore = (reducers = getReducers, initialState = {}, middlewares = getMiddlewares) => { + let getReducers; + if (typeof reducers === 'function') { + getReducers = reducers; + reducers = getReducers(); + } + if (typeof reducers === 'object') { + // allow to tolerate empty reducers + //@see https://github.com/reduxjs/redux/issues/968 + reducers = !_isEmpty(reducers) ? combineReducers(reducers) : () => {} + } + let getMiddlewares; + if (typeof middlewares === 'function') { + getMiddlewares = middlewares; + middlewares = getMiddlewares(); + } + middlewares = Array.isArray(middlewares) ? middlewares : [middlewares]; + const store = createStore( + // reducers + reducers, + // initial state + initialState, + // middlewares + compose( + applyMiddleware(...middlewares), + typeof window !== 'undefined' && window.devToolsExtension ? window.devToolsExtension() : f => f, + ), + ); + store.reload = function reload(options = {}) { + if (typeof options.reducers === 'function') { + getReducers = options.reducers; + options.reducers = undefined; + } + if (!options.reducers && getReducers) { + options.reducers = getReducers(); + } + if (options.reducers) { + reducers = typeof options.reducers === 'object' ? combineReducers(options.reducers) : options.reducers; + } + this.replaceReducer(reducers); + return store; + }; + return store; +}; + +// action +// **Notes: client side, addAction to browser** +// **Notes: server side, addAction to server share with every req** +let actions = {}; +export const addAction = (addedAction) => { + actions = { ...actions, ...addedAction }; + return actions; +}; +export const getActions = () => actions; +// reducers +// **Notes: client side, addReducer to browser** +// **Notes: server side, addReducer to server share with every req** +let reducers = {}; + +export const addReducer = (addedReducer) => { + reducers = { ...reducers, ...addedReducer }; + return reducers; +}; +export const getReducers = () => reducers; +// middlewares +// **Notes: client side, addMiddleware to browser** +// **Notes: server side, addMiddleware to server share with every req** +let middlewares = []; + +export const addMiddleware = (middlewareOrMiddlewareArray, options = {}) => { + const addedMiddleware = Array.isArray(middlewareOrMiddlewareArray) ? middlewareOrMiddlewareArray : [middlewareOrMiddlewareArray]; + if (options.unshift) { + middlewares = [...addedMiddleware, ...middlewares]; + } else { + middlewares = [...middlewares, ...addedMiddleware]; + } + return middlewares; +}; +export const getMiddlewares = () => middlewares; + + + +let store +export const initStore = () => { + if (!store) { + store = configureStore() + } + return store +} +export const getStore = () => store diff --git a/packages/vulcan-redux/lib/server/main.js b/packages/vulcan-redux/lib/server/main.js new file mode 100644 index 000000000..b1216bb37 --- /dev/null +++ b/packages/vulcan-redux/lib/server/main.js @@ -0,0 +1,4 @@ +import setupRedux from './setupRedux'; +setupRedux(); + +export * from '../modules/index'; diff --git a/packages/vulcan-redux/lib/server/setupRedux.js b/packages/vulcan-redux/lib/server/setupRedux.js new file mode 100644 index 000000000..52e9df38d --- /dev/null +++ b/packages/vulcan-redux/lib/server/setupRedux.js @@ -0,0 +1,13 @@ +import React from 'react' +import { Provider } from 'react-redux' +import { addCallback } from 'meteor/vulcan:core' +import { initStore } from '../modules/redux' + +const setupRedux = () => { + const store = initStore() + addCallback('router.server.wrapper', + function ReduxStoreProvider(app) { + return {app} + }); +} +export default setupRedux \ No newline at end of file diff --git a/packages/vulcan-redux/package.js b/packages/vulcan-redux/package.js new file mode 100644 index 000000000..c0b010cd4 --- /dev/null +++ b/packages/vulcan-redux/package.js @@ -0,0 +1,18 @@ +Package.describe({ + name: 'vulcan:redux', + summary: 'Add Redux to Vulcan.', + version: '1.12.8', + git: 'https://github.com/VulcanJS/Vulcan.git' +}); + +Package.onUse(function(api) { + api.versionsFrom('1.6.1'); + + + api.use([ + 'vulcan:core@1.12.8', + ]); + + api.mainModule('lib/server/main.js', 'server'); + api.mainModule('lib/client/main.js', 'client'); +}); \ No newline at end of file diff --git a/packages/vulcan-styled-components/lib/client/main.js b/packages/vulcan-styled-components/lib/client/main.js new file mode 100644 index 000000000..172944a07 --- /dev/null +++ b/packages/vulcan-styled-components/lib/client/main.js @@ -0,0 +1 @@ +export * from '../modules/index'; diff --git a/packages/vulcan-styled-components/lib/modules/index.js b/packages/vulcan-styled-components/lib/modules/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/packages/vulcan-styled-components/lib/server/main.js b/packages/vulcan-styled-components/lib/server/main.js new file mode 100644 index 000000000..da2cb405c --- /dev/null +++ b/packages/vulcan-styled-components/lib/server/main.js @@ -0,0 +1,4 @@ +import setupStyledComponents from './setupStyledComponents'; +setupStyledComponents(); + +export * from '../modules/index'; diff --git a/packages/vulcan-styled-components/lib/server/setupStyledComponents.js b/packages/vulcan-styled-components/lib/server/setupStyledComponents.js new file mode 100644 index 000000000..24443f75f --- /dev/null +++ b/packages/vulcan-styled-components/lib/server/setupStyledComponents.js @@ -0,0 +1,23 @@ +// Setup SSR +import { ServerStyleSheet } from 'styled-components' +import { addCallback } from 'meteor/vulcan:core'; + +const setupStyledComponents = () => { + addCallback('router.server.wrapper', function collectStyles(app, { context }) { + const stylesheet = new ServerStyleSheet(); + // @see https://www.styled-components.com/docs/advanced/#example + const wrappedApp = stylesheet.collectStyles(app) + // store the stylesheet to reuse it later + context.stylesheet = stylesheet + return wrappedApp + + }) + + addCallback('router.server.postRender', function appendStyleTags(sink, { context }) { + sink.appendToHead(context.stylesheet.getStyleTags()) + return sink + }) +} + + +export default setupStyledComponents \ No newline at end of file diff --git a/packages/vulcan-styled-components/package.js b/packages/vulcan-styled-components/package.js new file mode 100644 index 000000000..eeb941acf --- /dev/null +++ b/packages/vulcan-styled-components/package.js @@ -0,0 +1,17 @@ +Package.describe({ + name: 'vulcan:styled-components', + summary: 'Add Styled Components to Vulcan.', + version: '1.12.8', + git: 'https://github.com/VulcanJS/Vulcan.git' +}); + +Package.onUse(function(api) { + api.versionsFrom('1.6.1'); + + api.use([ + 'vulcan:core@1.12.8', + ]); + + api.mainModule('lib/server/main.js', 'server'); + api.mainModule('lib/client/main.js', 'client'); +}); \ No newline at end of file