Skip to content

Commit

Permalink
Merge pull request #6656 from apollographql/revamp-commonjs-interdepe…
Browse files Browse the repository at this point in the history
…ndencies

Rework interdependencies between @apollo/client/* entry points.
  • Loading branch information
benjamn authored Jul 20, 2020
2 parents 8d24498 + 43fc12d commit 7993a77
Show file tree
Hide file tree
Showing 151 changed files with 1,564 additions and 975 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## Apollo Client 3.1.0

## Bug Fixes

- Rework interdependencies between `@apollo/client/*` entry points, so that CommonJS and ESM modules are supported equally well, without any duplication of shared code. <br/>
[@benjamn](https://github.com/benjamn) in [#6058](https://github.com/apollographql/apollo-client/pull/6058)

## Apollo Client 3.0.2

## Bug Fixes
Expand Down
109 changes: 109 additions & 0 deletions config/entryPoints.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
const entryPoints = [
{ dirs: [], bundleName: "main" },
{ dirs: ['cache'] },
{ dirs: ['core'] },
{ dirs: ['errors'] },
{ dirs: ['link', 'batch'] },
{ dirs: ['link', 'batch-http'] },
{ dirs: ['link', 'context'] },
{ dirs: ['link', 'core'] },
{ dirs: ['link', 'error'] },
{ dirs: ['link', 'http'] },
{ dirs: ['link', 'retry'] },
{ dirs: ['link', 'schema'] },
{ dirs: ['link', 'utils'] },
{ dirs: ['link', 'ws'] },
{ dirs: ['react'] },
{ dirs: ['react', 'components'] },
{ dirs: ['react', 'context'] },
{ dirs: ['react', 'data'] },
{ dirs: ['react', 'hoc'] },
{ dirs: ['react', 'hooks'] },
{ dirs: ['react', 'parser'] },
{ dirs: ['react', 'ssr'] },
{ dirs: ['utilities'] },
{ dirs: ['testing'], extensions: [".js", ".jsx"] },
];

const lookupTrie = Object.create(null);
entryPoints.forEach(info => {
let node = lookupTrie;
info.dirs.forEach(dir => {
const dirs = node.dirs || (node.dirs = Object.create(null));
node = dirs[dir] || (dirs[dir] = { isEntry: false });
});
node.isEntry = true;
});

exports.forEach = function(callback, context) {
entryPoints.forEach(callback, context);
};

exports.map = function map(callback, context) {
return entryPoints.map(callback, context);
};

const path = require("path").posix;

exports.check = function (id, parentId) {
const resolved = path.resolve(path.dirname(parentId), id);
const importedParts = partsAfterDist(resolved);

if (importedParts) {
const entryPointIndex = lengthOfLongestEntryPoint(importedParts);
if (entryPointIndex === importedParts.length) {
return true;
}

if (entryPointIndex >= 0) {
const parentParts = partsAfterDist(parentId);
const parentEntryPointIndex = lengthOfLongestEntryPoint(parentParts);
const sameEntryPoint =
entryPointIndex === parentEntryPointIndex &&
arraysEqualUpTo(importedParts, parentParts, entryPointIndex);

// If the imported ID and the parent ID have the same longest entry
// point prefix, then this import is safely confined within that
// entry point. Returning false lets Rollup know this import is not
// external, and can be bundled into the CJS bundle that we build
// for this shared entry point.
if (sameEntryPoint) {
return false;
}

console.warn(`Risky cross-entry-point nested import of ${id} in ${
partsAfterDist(parentId).join("/")
}`);
}
}

return false;
};

function partsAfterDist(id) {
const parts = id.split(path.sep);
const distIndex = parts.lastIndexOf("dist");
if (distIndex >= 0) {
return parts.slice(distIndex + 1);
}
}

function lengthOfLongestEntryPoint(parts) {
let node = lookupTrie;
let longest = -1;
for (let i = 0; node && i < parts.length; ++i) {
if (node.isEntry) longest = i;
node = node.dirs && node.dirs[parts[i]];
}
if (node && node.isEntry) {
return parts.length;
}
return longest;
}

function arraysEqualUpTo(a, b, end) {
for (let i = 0; i < end; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}
6 changes: 6 additions & 0 deletions config/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const { compilerOptions } = require("../tsconfig.json");

module.exports = {
rootDir: '..',
transform: {
Expand All @@ -6,6 +8,10 @@ module.exports = {
globals: {
'ts-jest': {
diagnostics: true,
tsConfig: {
...compilerOptions,
allowJs: true,
},
},
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
Expand Down
143 changes: 17 additions & 126 deletions config/prepareDist.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// store it in the appropriate dist sub-directory.

const fs = require('fs');
const path = require('path');
const recast = require('recast');

const distRoot = `${__dirname}/../dist`;
Expand All @@ -20,6 +21,7 @@ const distRoot = `${__dirname}/../dist`;
/* @apollo/client */

const packageJson = require('../package.json');
const entryPoints = require('./entryPoints.js');

// The root package.json is marked as private to prevent publishing
// from happening in the root of the project. This sets the package back to
Expand Down Expand Up @@ -52,134 +54,23 @@ const destDir = `${srcDir}/dist`;
fs.copyFileSync(`${srcDir}/README.md`, `${destDir}/README.md`);
fs.copyFileSync(`${srcDir}/LICENSE`, `${destDir}/LICENSE`);


/*
* @apollo/client/core
* @apollo/client/cache
* @apollo/client/utilities
* @apollo/client/react/ssr
* @apollo/client/react/hoc
* @apollo/client/react/components
*/

function buildPackageJson(bundleName, entryPoint) {
return JSON.stringify({
name: `@apollo/client/${entryPoint || bundleName}`,
main: `${bundleName}.cjs.js`,
module: 'index.js',
types: 'index.d.ts',
}, null, 2) + "\n";
}

function loadExportNames(bundleName) {
const indexSrc =
fs.readFileSync(`${distRoot}/${bundleName}/index.js`);
const exportNames = [];
recast.visit(recast.parse(indexSrc), {
visitExportSpecifier(path) {
exportNames.push(path.value.exported.name);
return false;
},
});
return exportNames;
}

function writeCjsIndex(bundleName, exportNames, includeNames = true) {
const filterPrefix = includeNames ? '' : '!';
fs.writeFileSync(`${distRoot}/${bundleName}/${bundleName}.cjs.js`, [
"var allExports = require('../apollo-client.cjs');",
`var names = new Set(${JSON.stringify(exportNames)});`,
"Object.keys(allExports).forEach(function (name) {",
` if (${filterPrefix}names.has(name)) {`,
" exports[name] = allExports[name];",
" }",
"});",
"",
].join('\n'));
}

// Create individual bundle package.json files, storing them in their
// associated dist directory. This helps provide a way for the Apollo Client
// core to be used without React, as well as AC's cache, utilities, SSR,
// components, HOC, and various links to be used by themselves, via CommonJS
// entry point files that only include the exports needed for each bundle.

// @apollo/client/core
fs.writeFileSync(`${distRoot}/core/package.json`, buildPackageJson('core'));
writeCjsIndex('core', loadExportNames('react'), false);

// @apollo/client/cache
fs.writeFileSync(`${distRoot}/cache/package.json`, buildPackageJson('cache'));
writeCjsIndex('cache', loadExportNames('cache'));

// @apollo/client/utilities
fs.writeFileSync(
`${distRoot}/utilities/package.json`,
buildPackageJson('utilities')
);

// @apollo/client/react/ssr
fs.writeFileSync(
`${distRoot}/react/ssr/package.json`,
buildPackageJson('ssr', 'react/ssr')
);

// @apollo/client/react/components
fs.writeFileSync(
`${distRoot}/react/components/package.json`,
buildPackageJson('components', 'react/components')
);

// @apollo/client/react/hoc
fs.writeFileSync(
`${distRoot}/react/hoc/package.json`,
buildPackageJson('hoc', 'react/hoc')
);

// @apollo/client/link/batch
fs.writeFileSync(
`${distRoot}/link/batch/package.json`,
buildPackageJson('batch', 'link/batch')
);

// @apollo/client/link/batch-http
fs.writeFileSync(
`${distRoot}/link/batch-http/package.json`,
buildPackageJson('batch-http', 'link/batch-http')
);

// @apollo/client/link/context
fs.writeFileSync(
`${distRoot}/link/context/package.json`,
buildPackageJson('context', 'link/context')
);

// @apollo/client/link/error
fs.writeFileSync(
`${distRoot}/link/error/package.json`,
buildPackageJson('error', 'link/error')
);

// @apollo/client/link/retry
fs.writeFileSync(
`${distRoot}/link/retry/package.json`,
buildPackageJson('retry', 'link/retry')
);

// @apollo/client/link/schema
fs.writeFileSync(
`${distRoot}/link/schema/package.json`,
buildPackageJson('schema', 'link/schema')
);

// @apollo/client/link/ws
fs.writeFileSync(
`${distRoot}/link/ws/package.json`,
buildPackageJson('ws', 'link/ws')
);

// @apollo/client/link/http
fs.writeFileSync(
`${distRoot}/link/http/package.json`,
buildPackageJson('http', 'link/http')
);
entryPoints.forEach(function buildPackageJson({
dirs,
bundleName = dirs[dirs.length - 1],
}) {
if (!dirs.length) return;
fs.writeFileSync(
path.join(distRoot, ...dirs, 'package.json'),
JSON.stringify({
name: path.posix.join('@apollo', 'client', ...dirs),
main: `${bundleName}.cjs.js`,
module: 'index.js',
types: 'index.d.ts',
}, null, 2) + "\n",
);
});
Loading

0 comments on commit 7993a77

Please sign in to comment.