123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- /* -----------------------------------------------------------------------------
- | Copyright (c) Jupyter Development Team.
- | Distributed under the terms of the Modified BSD License.
- |----------------------------------------------------------------------------*/
- import MiniCssExtractPlugin from 'mini-css-extract-plugin';
- import * as webpack from 'webpack';
- import * as fs from 'fs-extra';
- import * as glob from 'glob';
- import * as path from 'path';
- import { readJSONFile } from '@jupyterlab/buildutils';
- /**
- * A namespace for JupyterLab build utilities.
- */
- export namespace Build {
- /**
- * The options used to ensure a root package has the appropriate
- * assets for its JupyterLab extension packages.
- */
- export interface IEnsureOptions {
- /**
- * The output directory where the build assets should reside.
- */
- output: string;
- /**
- * The directory for the schema directory, defaults to the output directory.
- */
- schemaOutput?: string;
- /**
- * The directory for the theme directory, defaults to the output directory
- */
- themeOutput?: string;
- /**
- * The names of the packages to ensure.
- */
- packageNames: ReadonlyArray<string>;
- /**
- * The package paths to ensure.
- */
- packagePaths?: ReadonlyArray<string>;
- }
- /**
- * The JupyterLab extension attributes in a module.
- */
- export interface ILabExtension {
- /**
- * Indicates whether the extension is a standalone extension.
- *
- * #### Notes
- * If `true`, the `main` export of the package is used. If set to a string
- * path, the export from that path is loaded as a JupyterLab extension. It
- * is possible for one package to have both an `extension` and a
- * `mimeExtension` but they cannot be identical (i.e., the same export
- * cannot be declared both an `extension` and a `mimeExtension`).
- */
- readonly extension?: boolean | string;
- /**
- * Indicates whether the extension is a MIME renderer extension.
- *
- * #### Notes
- * If `true`, the `main` export of the package is used. If set to a string
- * path, the export from that path is loaded as a JupyterLab extension. It
- * is possible for one package to have both an `extension` and a
- * `mimeExtension` but they cannot be identical (i.e., the same export
- * cannot be declared both an `extension` and a `mimeExtension`).
- */
- readonly mimeExtension?: boolean | string;
- /**
- * The local schema file path in the extension package.
- */
- readonly schemaDir?: string;
- /**
- * The local theme file path in the extension package.
- */
- readonly themePath?: string;
- }
- /**
- * A minimal definition of a module's package definition (i.e., package.json).
- */
- export interface IModule {
- /**
- * The JupyterLab metadata/
- */
- jupyterlab?: ILabExtension;
- /**
- * The main entry point in a module.
- */
- main?: string;
- /**
- * The name of a module.
- */
- name: string;
- }
- /**
- * Ensures that the assets of plugin packages are populated for a build.
- *
- * @ Returns An array of lab extension config data.
- */
- export function ensureAssets(
- options: IEnsureOptions
- ): webpack.Configuration[] {
- const {
- output,
- schemaOutput = output,
- themeOutput = output,
- packageNames
- } = options;
- const themeConfig: webpack.Configuration[] = [];
- const packagePaths: string[] = options.packagePaths?.slice() || [];
- // Get the CSS imports.
- // We must import the application CSS first.
- // The order of the rest does not matter.
- // We explicitly ignore themes so they can be loaded dynamically.
- let cssImports: Array<string> = [];
- let appCSS = '';
- packageNames.forEach(name => {
- packagePaths.push(
- path.dirname(require.resolve(path.join(name, 'package.json')))
- );
- });
- packagePaths.forEach(packagePath => {
- const packageDataPath = require.resolve(
- path.join(packagePath, 'package.json')
- );
- const packageDir = path.dirname(packageDataPath);
- const data = readJSONFile(packageDataPath);
- const name = data.name;
- const extension = normalizeExtension(data);
- const { schemaDir, themePath } = extension;
- // Handle styles.
- if (data.style) {
- if (data.name === '@jupyterlab/application-extension') {
- appCSS = name + '/' + data.style;
- } else if (!data.jupyterlab.themePath) {
- cssImports.push(name + '/' + data.style);
- }
- }
- // Handle schemas.
- if (schemaDir) {
- const schemas = glob.sync(
- path.join(path.join(packageDir, schemaDir), '*')
- );
- const destination = path.join(schemaOutput, 'schemas', name);
- // Remove the existing directory if necessary.
- if (fs.existsSync(destination)) {
- try {
- const oldPackagePath = path.join(destination, 'package.json.orig');
- const oldPackageData = readJSONFile(oldPackagePath);
- if (oldPackageData.version === data.version) {
- fs.removeSync(destination);
- }
- } catch (e) {
- fs.removeSync(destination);
- }
- }
- // Make sure the schema directory exists.
- fs.mkdirpSync(destination);
- // Copy schemas.
- schemas.forEach(schema => {
- const file = path.basename(schema);
- fs.copySync(schema, path.join(destination, file));
- });
- // Write the package.json file for future comparison.
- fs.copySync(
- path.join(packageDir, 'package.json'),
- path.join(destination, 'package.json.orig')
- );
- }
- if (!themePath) {
- return;
- }
- themeConfig.push({
- mode: 'production',
- entry: {
- index: path.join(packageDir, themePath)
- },
- output: {
- path: path.resolve(path.join(themeOutput, 'themes', name)),
- // we won't use these JS files, only the extracted CSS
- filename: '[name].js'
- },
- module: {
- rules: [
- {
- test: /\.css$/,
- use: [MiniCssExtractPlugin.loader, 'css-loader']
- },
- {
- test: /\.svg/,
- use: [{ loader: 'svg-url-loader', options: { encoding: 'none' } }]
- },
- {
- test: /\.(cur|png|jpg|gif|ttf|woff|woff2|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
- use: [{ loader: 'url-loader', options: { limit: 10000 } }]
- }
- ]
- },
- plugins: [
- new MiniCssExtractPlugin({
- // Options similar to the same options in webpackOptions.output
- // both options are optional
- filename: '[name].css',
- chunkFilename: '[id].css'
- })
- ]
- });
- });
- // 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}');`;
- cssImports.forEach(cssImport => {
- cssContents += `\n@import url('~${cssImport}');`;
- });
- cssContents += '\n';
- 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' });
- return themeConfig;
- }
- /**
- * Returns JupyterLab extension metadata from a module.
- */
- export function normalizeExtension(module: IModule): ILabExtension {
- let { jupyterlab, main, name } = module;
- main = main || 'index.js';
- if (!jupyterlab) {
- throw new Error(`Module ${name} does not contain JupyterLab metadata.`);
- }
- let { extension, mimeExtension, schemaDir, themePath } = jupyterlab;
- extension = extension === true ? main : extension;
- mimeExtension = mimeExtension === true ? main : mimeExtension;
- if (extension && mimeExtension && extension === mimeExtension) {
- const message = 'extension and mimeExtension cannot be the same export.';
- throw new Error(message);
- }
- return { extension, mimeExtension, schemaDir, themePath };
- }
- }
|