Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Named exports in umd builds #2141

Merged
merged 28 commits into from Mar 16, 2019
Merged

Named exports in umd builds #2141

merged 28 commits into from Mar 16, 2019

Conversation

talves
Copy link
Collaborator

@talves talves commented Mar 5, 2019

Summary

  • The solution to have valid umd builds for the libraries.
  • All libraries use React and ReactDOM as peer deps for reusable component support.

Would allow us to build the netlify-cms.js bundle from the umd buld (~+200k overhead added), but we can save that 200k by going direct to the code since we are handling the transforms and plugins within the base webpack monorepo setup anyway.

Test plan
netlify-cms
Make sure the netlify-cms.js bundle is working correctly (full tests).
Make sure all tests are running correctly.
Make sure all development workflow works correctly.

others: netlify-cms-
Run a development server with all script imports like the following example and make sure they run without a bundle:

  <script src="https://unpkg.com/react@16.8.2/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16.8.2/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/create-react-class@15.6.3/create-react-class.js"></script>
  <script src="/cms/netlify-cms-default-exports/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-editor-component-image/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-lib-auth/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-lib-util/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-media-library-uploadcare/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-media-library-cloudinary/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-ui-default/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-backend-bitbucket/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-backend-github/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-backend-gitlab/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-backend-git-gateway/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-backend-test/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-core/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-media-library-cloudinary/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-widget-boolean/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-widget-date/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-widget-map/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-widget-markdown/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-widget-number/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-widget-object/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-widget-relation/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-widget-select/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-widget-string/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-widget-text/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-backend-git-gateway/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-widget-datetime/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-widget-file/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-widget-list/dist/umd/index.js"></script>
  <script src="/cms/netlify-cms-widget-image/dist/umd/index.js"></script>

[Demo] of the above builds using this PR 馃敟

Detail

What this PR accomplishes

These build improvements fix the umd bundles in the monorepo for each library.

The current UMD target builds are broken. If you wanted to import from one of the monorepo published libraries and access them through an import, they would fail. It is the major reason we are using the code directly in the netlify-cms bundle.

There are 2 targets for a umd UMD (Universal Module Definition) library:

  • You can access them from a script <script src="/cms/netlify-cms-widget-image/dist/umd/index.js"></script>
    This allows you to access window.NetlifyCmsWidgetImage in this case from the browser. (think React)
  • They allow you to use them from es code imports
    This allows you to access the exports of the module import { Control, Preview } from 'netlify-cms-widget-image'

We are deriving the NetlifyCmsWidgetImage global namespace by using the library name, stripping the - and Capitalizing each section. This allows us to use webpack's bundler to name the umd export and be able to have them targeted within the modules for export globally.

The format in the above others: netlify-cms- test case above is a valid example of bringing the whole CMS into the browser without bundling, it is only a side affect of the builds, and allows for testing the builds, It would be recommended to create your own bundle from the libraries using a package manager. This way you have a managed set of code you can use to build a smaller bundle of the netlify-cms with targeted module versions.

Example

A netlify-cms bundle using:

  • git-gateway backend
  • the internal media library)
  • exports the CMS core for manual init and registration of custom widgets
  • could register your own widgets within this bundle also
import * as CMS from 'netlify-cms-core';

/* See note on React, ReactDOM as peers  */

/** Backend **/
import { Control as NetlifyCmsBackendGitGateway } from 'netlify-cms-backend-git-gateway';
CMS.registerBackend('git-gateway', NetlifyCmsBackendGitGateway);

/** Widgets */
import * as NetlifyCmsWidgetString from 'netlify-cms-widget-string';
import * as NetlifyCmsWidgetNumber from 'netlify-cms-widget-number';
import * as NetlifyCmsWidgetText from 'netlify-cms-widget-text';
import * as NetlifyCmsWidgetImage from 'netlify-cms-widget-image';
import * as NetlifyCmsWidgetFile from 'netlify-cms-widget-file';
import * as NetlifyCmsWidgetDate from 'netlify-cms-widget-date';
import * as NetlifyCmsWidgetDatetime from 'netlify-cms-widget-datetime';
import * as NetlifyCmsWidgetSelect from 'netlify-cms-widget-select';
import * as NetlifyCmsWidgetMarkdown from 'netlify-cms-widget-markdown';
import * as NetlifyCmsWidgetList from 'netlify-cms-widget-list';
import * as NetlifyCmsWidgetObject from 'netlify-cms-widget-object';
import * as NetlifyCmsWidgetRelation from 'netlify-cms-widget-relation';
import * as NetlifyCmsWidgetBoolean from 'netlify-cms-widget-boolean';
import * as NetlifyCmsWidgetMap from 'netlify-cms-widget-map';

CMS.registerWidget('string', NetlifyCmsWidgetString.controlComponent) NetlifyCmsWidgetString.previewComponent);
CMS.registerWidget('number', NetlifyCmsWidgetNumber.controlComponent) NetlifyCmsWidgetNumber.previewComponent);
CMS.registerWidget('text', NetlifyCmsWidgetText.controlComponent) NetlifyCmsWidgetText.previewComponent);
CMS.registerWidget('list', NetlifyCmsWidgetList.controlComponent) NetlifyCmsWidgetList.previewComponent);
CMS.registerWidget('markdown', NetlifyCmsWidgetMarkdown.controlComponent) NetlifyCmsWidgetMarkdown.previewComponent);
CMS.registerWidget('image', NetlifyCmsWidgetImage.controlComponent) NetlifyCmsWidgetImage.previewComponent);
CMS.registerWidget('file', NetlifyCmsWidgetFile.controlComponent) NetlifyCmsWidgetFile.previewComponent);
CMS.registerWidget('date', NetlifyCmsWidgetDate.controlComponent) NetlifyCmsWidgetDate.previewComponent);
CMS.registerWidget('datetime', NetlifyCmsWidgetDatetime.controlComponent) NetlifyCmsWidgetDatetime.previewComponent);
CMS.registerWidget('select', NetlifyCmsWidgetSelect.controlComponent) NetlifyCmsWidgetSelect.previewComponent);
CMS.registerWidget('object', NetlifyCmsWidgetObject.controlComponent) NetlifyCmsWidgetObject.previewComponent);
CMS.registerWidget('relation', NetlifyCmsWidgetRelation.controlComponent) NetlifyCmsWidgetRelation.previewComponent);
CMS.registerWidget('boolean', NetlifyCmsWidgetBoolean.controlComponent);
CMS.registerWidget('map', NetlifyCmsWidgetMap.controlComponent) NetlifyCmsWidgetMap.previewComponent);

/** Un-comment the next few lines to have an automatic start of the bundle just like `netlif-cms.js` */
// if (typeof window !== 'undefined') {
//   CMS.init();
// }

export default CMS;

Notes:

  • NetlifyCMS Default exports Includes React, ReactDOM as peers
    and includes the dependencies needed for your module.
    Make sure to include React, ReactDOM in your bundle or
    make them peer dependencies if you can import your library into a React app.

build note: size of bundle: 2.95 MiB vs 3.41 MiB

Thoughts about why this is Imperative!

  • Currently, the CMS does not support a bundle where it is a reusable react component and a single instance of React.
  • All the UMD builds should support the pattern of React, ReactDOM as peer dependencies. Including the core.
  • We should consider doing a build for the case a developer wants to just grab the whole thing and use it as a reusable component. This PR accomplishes that target very easily, because we can just add a simple module equal to netlify-cms.js with React and ReactDOM as peer dependencies.
  • The size of NetlifyCMS is becoming larger and larger with the addition of new default features. The size is due to a lot of unused code that could be targeted using the libraries directly and importing only what a project is using. This would not affect the netlify-cms.js default bundle, but allow developers to control the size of their cms bundle size.

- Hoist dev dependencies for build tools to root
- align all external versions to be the same
- add or remove missing peers
- add build:dev script for development testing
- bring rules to top config
- allow for multiple output targets [umd, cjs, ...]
- add externals for umd builds for consistant namespacing
- umd externals map names to their correct target
- Change src to coreSrc for clarity
- removes complexity of linking webconfig from netlify-cms to core to base
- The library needed to have named exports using umd in scripts
- uses main webpack config
- imports from new globals
- change core to export default registry and init
@netlify
Copy link

netlify bot commented Mar 5, 2019

Preview proposed changes to the CMS demo site in the link below:

Built with commit c9a9aea

https://deploy-preview-2141--cms-demo.netlify.com

default on umd exports would not be needed in script imports
The named exports are not exposed in the module libraries, only exported to the target `window` from within the module bundle.
We want to make sure we follow that pattern, even if we are using `/src` in the `netlify-cms-js` bundle.
@talves
Copy link
Collaborator Author

talves commented Mar 7, 2019

Follow Up (React as Peer):

The following code produces a bundle using the proposed umd bundled libraries with react and react-dom as a peer dependency (external).
Discussion needs to happen for whether to use this as a published library or have developers do this build.

import * as CMS from 'netlify-cms-core';
/** Backends */
import { Control as NetlifyCmsBackendGithub } from 'netlify-cms-backend-github';
import { Control as NetlifyCmsBackendGitlab } from 'netlify-cms-backend-gitlab';
import { Control as NetlifyCmsBackendGitGateway } from 'netlify-cms-backend-git-gateway';
import { Control as NetlifyCmsBackendBitbucket } from 'netlify-cms-backend-bitbucket';
import { Control as NetlifyCmsBackendTest } from 'netlify-cms-backend-test';
/** Widgets */
import * as NetlifyCmsWidgetString from 'netlify-cms-widget-string';
import * as NetlifyCmsWidgetNumber from 'netlify-cms-widget-number';
import * as NetlifyCmsWidgetText from 'netlify-cms-widget-text';
import * as NetlifyCmsWidgetImage from 'netlify-cms-widget-image';
import * as NetlifyCmsWidgetFile from 'netlify-cms-widget-file';
import * as NetlifyCmsWidgetDate from 'netlify-cms-widget-date';
import * as NetlifyCmsWidgetDatetime from 'netlify-cms-widget-datetime';
import * as NetlifyCmsWidgetSelect from 'netlify-cms-widget-select';
import * as NetlifyCmsWidgetMarkdown from 'netlify-cms-widget-markdown';
import * as NetlifyCmsWidgetList from 'netlify-cms-widget-list';
import * as NetlifyCmsWidgetObject from 'netlify-cms-widget-object';
import * as NetlifyCmsWidgetRelation from 'netlify-cms-widget-relation';
import * as NetlifyCmsWidgetBoolean from 'netlify-cms-widget-boolean';
import * as NetlifyCmsWidgetMap from 'netlify-cms-widget-map';
/** MediaLibraries */
import uploadcare from 'netlify-cms-media-library-uploadcare';
import cloudinary from 'netlify-cms-media-library-cloudinary';
/** EditorComponents */
import image from 'netlify-cms-editor-component-image';

/** Backends */
CMS.registerBackend('git-gateway', NetlifyCmsBackendGitGateway);
CMS.registerBackend('github', NetlifyCmsBackendGithub);
CMS.registerBackend('gitlab', NetlifyCmsBackendGitlab);
CMS.registerBackend('bitbucket', NetlifyCmsBackendBitbucket);
CMS.registerBackend('test-repo', NetlifyCmsBackendTest);
/** Widgets */
CMS.registerWidget('string', NetlifyCmsWidgetString.controlComponent) NetlifyCmsWidgetString.previewComponent);
CMS.registerWidget('number', NetlifyCmsWidgetNumber.controlComponent) NetlifyCmsWidgetNumber.previewComponent);
CMS.registerWidget('text', NetlifyCmsWidgetText.controlComponent) NetlifyCmsWidgetText.previewComponent);
CMS.registerWidget('list', NetlifyCmsWidgetList.controlComponent) NetlifyCmsWidgetList.previewComponent);
CMS.registerWidget('markdown', NetlifyCmsWidgetMarkdown.controlComponent) NetlifyCmsWidgetMarkdown.previewComponent);
CMS.registerWidget('image', NetlifyCmsWidgetImage.controlComponent) NetlifyCmsWidgetImage.previewComponent);
CMS.registerWidget('file', NetlifyCmsWidgetFile.controlComponent) NetlifyCmsWidgetFile.previewComponent);
CMS.registerWidget('date', NetlifyCmsWidgetDate.controlComponent) NetlifyCmsWidgetDate.previewComponent);
CMS.registerWidget('datetime', NetlifyCmsWidgetDatetime.controlComponent) NetlifyCmsWidgetDatetime.previewComponent);
CMS.registerWidget('select', NetlifyCmsWidgetSelect.controlComponent) NetlifyCmsWidgetSelect.previewComponent);
CMS.registerWidget('object', NetlifyCmsWidgetObject.controlComponent) NetlifyCmsWidgetObject.previewComponent);
CMS.registerWidget('relation', NetlifyCmsWidgetRelation.controlComponent) NetlifyCmsWidgetRelation.previewComponent);
CMS.registerWidget('boolean', NetlifyCmsWidgetBoolean.controlComponent);
CMS.registerWidget('map', NetlifyCmsWidgetMap.controlComponent) NetlifyCmsWidgetMap.previewComponent);
/** MediaLibraries */
CMS.registerMediaLibrary(uploadcare);
CMS.registerMediaLibrary(cloudinary);
/** EditorComponents */
CMS.registerEditorComponent(image);

export const NetlifyCmsReact = CMS;
export { CMS as default };

package.json dependencies

{
  "dependencies": {
    "create-react-class": "^15.6.3",
    "emotion": "^9.2.6",
    "immutable": "^3.7.6",
    "lodash": "^4.17.10",
    "moment": "^2.24.0",
    "netlify-cms-backend-bitbucket": "^2.0.0",
    "netlify-cms-backend-git-gateway": "^2.0.0",
    "netlify-cms-backend-github": "^2.0.0",
    "netlify-cms-backend-gitlab": "^2.0.0",
    "netlify-cms-backend-test": "^2.0.0",
    "netlify-cms-core": "^2.0.0",
    "netlify-cms-editor-component-image": "^2.0.0",
    "netlify-cms-media-library-cloudinary": "^1.0.0",
    "netlify-cms-media-library-uploadcare": "^0.3.0",
    "netlify-cms-widget-boolean": "^2.0.0",
    "netlify-cms-widget-date": "^2.0.0",
    "netlify-cms-widget-datetime": "^2.0.0",
    "netlify-cms-widget-file": "^2.0.0",
    "netlify-cms-widget-image": "^2.0.0",
    "netlify-cms-widget-list": "^2.0.0",
    "netlify-cms-widget-map": "^1.0.0",
    "netlify-cms-widget-markdown": "^2.0.0",
    "netlify-cms-widget-number": "^2.0.0",
    "netlify-cms-widget-object": "^2.0.0",
    "netlify-cms-widget-relation": "^2.0.0",
    "netlify-cms-widget-select": "^2.0.0",
    "netlify-cms-widget-string": "^2.0.0",
    "netlify-cms-widget-text": "^2.0.0",
    "prop-types": "^15.7.2",
    "react-immutable-proptypes": "^2.1.0"
  },
  "peerDependencies": {
    "react": "^16.8.1",
    "react-dom": "^16.8.1"
  }
}

@netlify
Copy link

netlify bot commented Mar 7, 2019

Preview proposed changes to netlifycms.org in the link below:

Built with commit c9a9aea

https://deploy-preview-2141--netlify-cms-www.netlify.com

@talves
Copy link
Collaborator Author

talves commented Mar 11, 2019

Parcel bundle example

Repository Example Here

Demo Here

Although the build for parcel takes longer than I would like. The bundle size is acceptable and there is a very easy setup to consume the libraries as dependencies and build a working example of a netlify-cms bundle using parcel.

@talves
Copy link
Collaborator Author

talves commented Mar 11, 2019

Rollup bundle example

Rollup sees the bundles as external UMD correctly and expects the library bundles would have to be included into a project as external libraries (same as the script imports example). To create a single bundle, there would need to be an ES module build where there is a module field (pkg.module). Rollup holds true to the standard module specs.

_Summary: _ Rollup would need to use the script imports until we get the solution for an ES module build. Otherwise you would just use the example given for script imports of the umd builds.

# Conflicts:
#	package.json
#	packages/netlify-cms-backend-bitbucket/package.json
#	packages/netlify-cms-backend-git-gateway/package.json
#	packages/netlify-cms-backend-github/package.json
#	packages/netlify-cms-backend-gitlab/package.json
#	packages/netlify-cms-backend-test/package.json
#	packages/netlify-cms-core/package.json
#	packages/netlify-cms-editor-component-image/package.json
#	packages/netlify-cms-lib-auth/package.json
#	packages/netlify-cms-lib-util/package.json
#	packages/netlify-cms-media-library-cloudinary/package.json
#	packages/netlify-cms-media-library-uploadcare/package.json
#	packages/netlify-cms-ui-default/package.json
#	packages/netlify-cms-widget-boolean/package.json
#	packages/netlify-cms-widget-date/package.json
#	packages/netlify-cms-widget-date/src/index.js
#	packages/netlify-cms-widget-datetime/package.json
#	packages/netlify-cms-widget-datetime/src/DateTimeControl.js
#	packages/netlify-cms-widget-datetime/src/index.js
#	packages/netlify-cms-widget-file/package.json
#	packages/netlify-cms-widget-image/package.json
#	packages/netlify-cms-widget-list/package.json
#	packages/netlify-cms-widget-map/package.json
#	packages/netlify-cms-widget-markdown/package.json
#	packages/netlify-cms-widget-number/package.json
#	packages/netlify-cms-widget-object/package.json
#	packages/netlify-cms-widget-relation/package.json
#	packages/netlify-cms-widget-relation/src/__tests__/relation.spec.js
#	packages/netlify-cms-widget-select/package.json
#	packages/netlify-cms-widget-string/package.json
#	packages/netlify-cms-widget-text/package.json
#	packages/netlify-cms/package.json
#	packages/netlify-cms/src/widgets.js
#	yarn.lock
@talves
Copy link
Collaborator Author

talves commented Mar 16, 2019

@erquhart Moved in the latest breaking changes. I hope we can get this in under alpha with these last changes.

In this refactor, I changed the names to match the API change in all the widgets, so it will be ready for the refactor and match the naming.

  • controlComponent
  • previewComponent

I like the no breaking API change. Seems like a solid way to register a widget.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants