Skip to content

Commit

Permalink
Generate index.js files, update style and sideEffects keys
Browse files Browse the repository at this point in the history
  • Loading branch information
jasongrout committed Dec 1, 2020
1 parent 3d81e1a commit 4fe7d8a
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 23 deletions.
53 changes: 45 additions & 8 deletions builder/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,23 +233,60 @@ export namespace Build {
});
});

// Template the CSS index file.
cssImports = cssImports.sort((a, b) => a.localeCompare(b));
let cssContents = '/* This is a generated file of CSS imports */';
cssContents +=
'\n/* It was generated by @jupyterlab/builder in Build.ensureAssets() */';
cssContents += `\n@import url('~${appCSS}');`;

let jsContents = ['/* This is a generated file of CSS imports */'];
jsContents.push(
'/* It was generated by @jupyterlab/builder in Build.ensureAssets() */'
);
function cssJS(cssImport: string): string | undefined {
const ext = path.extname(cssImport);
if (ext === '.css') {
let jsFile = `${cssImport.slice(0, cssImport.length - ext.length)}.js`;
if (fs.existsSync(jsFile)) {
return jsFile;
}
}
}

let jsImport;
if ((jsImport = cssJS(appCSS))) {
jsContents.push(`import '${jsImport}';`);
}
cssImports.forEach(cssImport => {
cssContents += `\n@import url('~${cssImport}');`;
if ((jsImport = cssJS(cssImport))) {
jsContents.push(`import '${jsImport}';`);
} else {
jsContents.push(`// import '${cssImport}';`);
}
});
cssContents += '\n';
jsContents.push('');
const indexCSSJSPath = path.join(output, 'imports.js');

// Template the CSS index file.
let cssContents = ['/* This is a generated file of CSS imports */'];
cssContents.push(
'/* It was generated by @jupyterlab/builder in Build.ensureAssets() */'
);
if (appCSS) {
cssContents.push(`@import url('~${appCSS}');`);
}
cssImports.forEach(cssImport => {
cssContents.push(`@import url('~${cssImport}');`);
});
cssContents.push('');
const indexCSSPath = path.join(output, 'imports.css');

// Make sure the output dir exists before writing to it.
if (!fs.existsSync(output)) {
fs.mkdirSync(output);
}
fs.writeFileSync(indexCSSPath, cssContents, { encoding: 'utf8' });
fs.writeFileSync(indexCSSPath, cssContents.join('\n'), {
encoding: 'utf8'
});
fs.writeFileSync(indexCSSJSPath, jsContents.join('\n'), {
encoding: 'utf8'
});

return themeConfig;
}
Expand Down
134 changes: 119 additions & 15 deletions buildutils/src/ensure-package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ const ICON_CSS_CLASSES_TEMPLATE = `
{{iconCSSDeclarations}}
`;

/**
* Replace the extension on a file path with a new extension newExt
*
* changeExt('some/path/file.ext', '.newext') -> 'some/path/file.newext'
* changeExt('some/path/file.ext') -> 'some/path/file'
*/
function changeExt(file: string, newExt: string = '') {
return `${file.slice(0, file.length - path.extname(file).length)}${newExt}`;
}

/**
* Ensure the integrity of a package.
*
Expand Down Expand Up @@ -183,24 +193,77 @@ export async function ensurePackage(
await Promise.all(promises);

// Template the CSS index file.
if (cssImports && fs.existsSync(path.join(pkgPath, 'style/base.css'))) {
let cssIndexContents: string[] = [];
if (
cssImports.length > 0 ||
fs.existsSync(path.join(pkgPath, 'style/base.css'))
) {
const funcName = 'ensurePackage';
let cssIndexContents = utils.fromTemplate(
HEADER_TEMPLATE,
{ funcName },
{ end: '' }
cssIndexContents.push(
utils.fromTemplate(HEADER_TEMPLATE, { funcName }, { end: '' })
);
cssImports.forEach(cssImport => {
cssIndexContents += `\n@import url('~${cssImport}');`;
cssIndexContents.push(`@import url('~${cssImport}');`);
});
cssIndexContents += "\n\n@import url('./base.css');\n";
if (fs.existsSync(path.join(pkgPath, 'style/base.css'))) {
cssIndexContents.push("\n@import url('./base.css');");
}
cssIndexContents.push('');

// write out cssIndexContents, if needed
const cssIndexPath = path.join(pkgPath, 'style/index.css');
if (!fs.existsSync(cssIndexPath)) {
fs.ensureFileSync(cssIndexPath);
}
messages.push(...ensureFile(cssIndexPath, cssIndexContents, false));
messages.push(
...ensureFile(cssIndexPath, cssIndexContents.join('\n'), false)
);
}

function cssJS(cssImport: string): string | undefined {
const ext = path.extname(cssImport);
if (ext === '.css') {
try {
let jsImport = changeExt(cssImport);
// errors if we do not find it
require.resolve(jsImport);
return jsImport;
} catch (err) {
/* no-op */
}
}
}

// Template the CSS JS index file.
let jsIndexContents: string[] = [];
if (
cssImports.length > 0 ||
fs.existsSync(path.join(pkgPath, 'style/base.css'))
) {
const funcName = 'ensurePackage';
jsIndexContents.push(
utils.fromTemplate(HEADER_TEMPLATE, { funcName }, { end: '' })
);
let jsImport: string | undefined;
cssImports.forEach(cssImport => {
if ((jsImport = cssJS(cssImport))) {
jsIndexContents.push(`import '${jsImport}';`);
} else {
jsIndexContents.push(`import '${cssImport}';`);
}
});
if (fs.existsSync(path.join(pkgPath, 'style/base.css'))) {
jsIndexContents.push("\nimport './base.css';");
}
jsIndexContents.push('');
// write out jsIndexContents, if needed
const jsIndexPath = path.join(pkgPath, 'style/index.js');
if (!fs.existsSync(jsIndexPath)) {
fs.ensureFileSync(jsIndexPath);
}
messages.push(
...ensureFile(jsIndexPath, jsIndexContents.join('\n'), false)
);
}

// Look for unused packages
Expand Down Expand Up @@ -338,17 +401,36 @@ export async function ensurePackage(

// Ensure that the `style` directories match what is in the `package.json`
const styles = glob.sync(path.join(pkgPath, 'style', '**/*.*'));
for (const style of styles) {
if (!published.has(style)) {
messages.push(`Style file ${style} not published in ${pkgPath}`);
}
}

// If we have styles, ensure that 'style' field is declared
const styleIndex: { [key: string]: string } = {};
// If we have styles, ensure that 'style' field is declared and we have a
// corresponding js file.
if (styles.length > 0) {
if (data.style === undefined) {
data.style = 'style/index.css';
}
styleIndex[path.join(pkgPath, data.style)] = data.style;
let jsFile = changeExt(data.style, '.js');
if (fs.existsSync(path.join(pkgPath, jsFile))) {
styleIndex[path.join(pkgPath, jsFile)] = jsFile;
} else {
messages.push(
`Style file ${data.style} has no corresponding js import file ${jsFile}`
);
}
} else {
// Delete the style field
delete data.style;
}

for (const style of styles) {
if (!published.has(style)) {
// Automatically add the style index files
if (data.files !== undefined && styleIndex[style] !== undefined) {
data.files.push(styleIndex[style]);
} else {
messages.push(`Style file ${style} not published in ${pkgPath}`);
}
}
}

// Ensure that sideEffects are declared, and that any styles are covered
Expand All @@ -359,6 +441,28 @@ export async function ensurePackage(
);
} else if (data.sideEffects === false) {
messages.push(`Style files not included in sideEffects in ${pkgPath}`);
} else if (data.sideEffects !== true) {
// Check to see if all .js and .css style files are listed in sideEffects
const sideEffects = new Set<string>(
data.sideEffects
? data.sideEffects.reduce((acc: string[], curr: string) => {
return acc.concat(glob.sync(path.join(pkgPath, curr)));
}, [])
: []
);
for (const style of styles) {
let ext = path.extname(style);
if (['.js', '.css'].includes(ext) && !sideEffects.has(style)) {
// If it is the data.style or corresponding js file, just add it to sideEffects
if (styleIndex[style] !== undefined) {
data.sideEffects.push(styleIndex[style]);
} else {
messages.push(
`Style file ${style} not covered by sideEffects globs in ${pkgPath}`
);
}
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions tsconfig.eslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"include": [
"packages/**/src/**/*",
"packages/**/test/**/*",
"packages/**/style/**/*",
"testutils/src/*",
"builder/**/*",
"buildutils/**/*",
Expand Down

0 comments on commit 4fe7d8a

Please sign in to comment.