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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement/issue 435 deeply nested relative paths #618

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 39 additions & 29 deletions packages/cli/src/config/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const postcssImport = require('postcss-import');
const pluginNodeModules = require('../plugins/resource/plugin-node-modules');
const pluginResourceStandardJavaScript = require('../plugins/resource/plugin-standard-javascript');
const tokenSuffix = 'scratch';
const tokenNodeModules = 'node_modules/';
const tokenNodeModules = 'node_modules';

const hashString = (queryKeysString) => {
let h = 0;
Expand Down Expand Up @@ -183,19 +183,19 @@ function greenwoodHtmlPlugin(compilation) {
// console.debug('dont emit ', parsedAttributes.src);
} else {
const { src } = parsedAttributes;

mappedScripts.set(src, true);

const srcPath = src.replace('../', './');
const basePath = srcPath.indexOf(tokenNodeModules) >= 0
const absoluteSrc = `${path.normalize(src.replace(/\.\.\//g, '').replace('./', ''))}`;
const basePath = absoluteSrc.indexOf(tokenNodeModules) >= 0
? projectDirectory
: userWorkspace;
const source = fs.readFileSync(path.join(basePath, srcPath), 'utf-8');

const id = path.join(basePath, absoluteSrc);
const source = fs.readFileSync(id, 'utf-8');

mappedScripts.set(absoluteSrc, true);

this.emitFile({
type: 'chunk',
id: srcPath.replace('/node_modules', path.join(projectDirectory, tokenNodeModules)),
name: srcPath.split('/')[srcPath.split('/').length - 1].replace('.js', ''),
id,
name: absoluteSrc.split(`${path.sep}`)[absoluteSrc.split(`${path.sep}`).length - 1].replace('.js', ''),
source
});
}
Expand Down Expand Up @@ -227,7 +227,7 @@ function greenwoodHtmlPlugin(compilation) {
headLinks.forEach((linkTag) => {
const parsedAttributes = parseTagForAttributes(linkTag);

// handle <link rel="stylesheet" src="./some/path.css"></link>
// handle <link rel="stylesheet" href="./some/path.css"></link>
if (!isRemoteUrl(parsedAttributes.href) && parsedAttributes.rel === 'stylesheet' && !mappedStyles[parsedAttributes.href]) {
let { href } = parsedAttributes;

Expand All @@ -238,22 +238,20 @@ function greenwoodHtmlPlugin(compilation) {
const basePath = href.indexOf(tokenNodeModules) >= 0
? projectDirectory
: userWorkspace;
const filePath = path.join(basePath, href.replace('../', './'));
const absoluteHref = href.replace(/\.\.\//g, '').replace('./', '');
const filePath = path.join(basePath, absoluteHref);
const source = fs.readFileSync(filePath, 'utf-8');
const to = `${outputDir}/${href}`;
const to = `${outputDir}/${absoluteHref}`;
const hash = hashString(source);
const fileName = href
.replace('.css', `.${hash.slice(0, 8)}.css`)
.replace('../', '')
.replace('./', '');
const fileName = absoluteHref.replace('.css', `.${hash.slice(0, 8)}.css`);

if (!fs.existsSync(path.dirname(to)) && href.indexOf(tokenNodeModules) < 0) {
fs.mkdirSync(path.dirname(to), {
recursive: true
});
}

mappedStyles[parsedAttributes.href] = {
mappedStyles[fileName] = {
type: 'asset',
fileName: fileName.indexOf(tokenNodeModules) >= 0
? path.basename(fileName)
Expand Down Expand Up @@ -281,7 +279,7 @@ function greenwoodHtmlPlugin(compilation) {
const result = await postcss()
.use(postcssImport())
.process(source, {
from: path.join(basePath, asset.name)
from: path.join(basePath, asset.name.replace(/\.\.\//g, ''))
});

asset.source = result.css;
Expand Down Expand Up @@ -324,16 +322,28 @@ function greenwoodHtmlPlugin(compilation) {
const facadeModuleId = bundles[innerBundleId].facadeModuleId
? bundles[innerBundleId].facadeModuleId.replace(/\\/g, '/')
: bundles[innerBundleId].facadeModuleId;
let pathToMatch = src.replace('../', '').replace('./', '');

// special handling for node_modules paths
if (pathToMatch.indexOf(tokenNodeModules) >= 0) {
pathToMatch = pathToMatch.replace(`/${tokenNodeModules}`, '');

const pathToMatchPieces = pathToMatch.split('/');

pathToMatch = pathToMatch.replace(tokenNodeModules, '');
pathToMatch = pathToMatch.replace(`${pathToMatchPieces[0]}/`, '');
let pathToMatch = src.replace(/\.\.\//g, '').replace('./', '');

/*
* this is an odd issue related to symlinking in our Greenwood monorepo when building the website
* and managing packages that we create as "virtaul" modules, like for the mpa router
*
* ex. import @greenwood/router/router.js -> /node_modules/@greenwood/cli/src/lib/router.js
*
* when running our tests, which better emulates a real user
* facadeModuleId will be in node_modules, which is like how it would be for a user:
* /node_modules/@greenwood/cli/src/lib/router.js
*
* however, when building our website, where symlinking points back to our packages/ directory
* facadeModuleId will look like this:
* /<workspace>/greenwood/packages/cli/src/lib/router.js
*
* so we need to massage pathToMatch a bit for Rollup for our internal development
* pathToMatch (before): /node_modules/@greenwood/cli/src/lib/router.js
* pathToMatch (after): /cli/src/lib/router.js
*/
if (facadeModuleId && facadeModuleId.indexOf(tokenNodeModules) < 0 && fs.existsSync(path.join(projectDirectory, pathToMatch))) {
pathToMatch = pathToMatch.replace(/\/node_modules\/@greenwood\//, '/');
}

if (facadeModuleId && facadeModuleId.indexOf(pathToMatch) > 0) {
Expand Down
26 changes: 26 additions & 0 deletions packages/cli/src/lib/resource-interface.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const fs = require('fs');
const path = require('path');

class ResourceInterface {
Expand All @@ -8,6 +9,31 @@ class ResourceInterface {
this.contentType = '';
}

// get rid of things like query string parameters
// that will break when trying to use with fs
getBareUrlPath(url) {
return url.replace(/\?(.*)/, '');
}

// turn relative paths into relatively absolute based on a known root directory
// e.g. "../styles/theme.css" -> `${userWorkspace}/styles/theme.css`
resolveRelativeUrl(root, url) {
let reducedUrl;

url.split('/')
.filter((segment) => segment !== '')
.reduce((acc, segment) => {
const reducedPath = url.replace(`${acc}/${segment}`, '');

if (path.extname(reducedPath) !== '' && fs.existsSync(path.join(root, reducedPath))) {
reducedUrl = reducedPath;
}
return `${acc}/${segment}`;
}, '');

return reducedUrl;
}

// test if this plugin should change a relative URL from the browser to an absolute path on disk
// like for node_modules/ resolution. not commonly needed by most resource plugins
// return true | false
Expand Down
16 changes: 6 additions & 10 deletions packages/cli/src/plugins/resource/plugin-node-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,17 +176,13 @@ class NodeModulesResource extends ResourceInterface {
}

async resolve(url) {
return new Promise((resolve, reject) => {
try {
const relativeUrl = url.replace(this.compilation.context.userWorkspace, '');
const nodeModulesUrl = path.join(process.cwd(), relativeUrl);
const { projectDirectory } = this.compilation.context;
const isAbsoluteNodeModulesFile = fs.existsSync(path.join(projectDirectory, url));
const nodeModulesUrl = isAbsoluteNodeModulesFile
? path.join(projectDirectory, url)
: path.join(projectDirectory, this.resolveRelativeUrl(projectDirectory, url));

resolve(nodeModulesUrl);
} catch (e) {
console.error(e);
reject(e);
}
});
return Promise.resolve(nodeModulesUrl);
}

async shouldServe(url) {
Expand Down
20 changes: 12 additions & 8 deletions packages/cli/src/plugins/resource/plugin-user-workspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,26 @@ class UserWorkspaceResource extends ResourceInterface {
this.extensions = ['*'];
}

getBareUrlPath(url) {
// get rid of things like query string parameters
return url.replace(/\?(.*)/, '');
}

async shouldResolve(url = '/') {
const { userWorkspace } = this.compilation.context;
const bareUrl = this.getBareUrlPath(url);
const isWorkspaceFile = fs.existsSync(path.join(this.compilation.context.userWorkspace, bareUrl));
const isAbsoluteWorkspaceFile = fs.existsSync(path.join(userWorkspace, bareUrl));
const workspaceUrl = isAbsoluteWorkspaceFile
? isAbsoluteWorkspaceFile || bareUrl === '/'
: this.resolveRelativeUrl(userWorkspace, bareUrl);

return Promise.resolve(isWorkspaceFile || bareUrl === '/');
return Promise.resolve(workspaceUrl);
}

async resolve(url = '/') {
const { userWorkspace } = this.compilation.context;

return new Promise(async (resolve, reject) => {
try {
const workspaceUrl = path.join(this.compilation.context.userWorkspace, this.getBareUrlPath(url));
const bareUrl = this.getBareUrlPath(url);
const workspaceUrl = fs.existsSync(path.join(userWorkspace, bareUrl))
? path.join(userWorkspace, bareUrl)
: path.join(userWorkspace, this.resolveRelativeUrl(userWorkspace, bareUrl));

resolve(workspaceUrl);
} catch (e) {
Expand Down
Loading