Skip to content

Cli8 #22

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

Cli8 #22

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
8 changes: 3 additions & 5 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
"node_modules/systemjs/dist/s.js",
"node_modules/systemjs/dist/extras/named-register.js",
"node_modules/systemjs/dist/extras/amd.js"
],
"es5BrowserSupport": true
]
},
"configurations": {
"production": {
Expand Down Expand Up @@ -142,8 +141,7 @@
"tsConfig": "projects/plugins/tsconfig.app.json",
"assets": [],
"styles": [],
"scripts": [],
"es5BrowserSupport": false
"scripts": []
},
"configurations": {
"production": {
Expand Down Expand Up @@ -206,4 +204,4 @@
}
},
"defaultProject": "angular-plugin-architecture"
}
}
12 changes: 12 additions & 0 deletions browserslist
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries

# You can see what browsers were selected by your queries by running:
# npx browserslist

> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.
2 changes: 1 addition & 1 deletion builders/builders.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"builders": {
"plugin": {
"class": "./plugin-builder",
"implementation": "./plugin-builder",
"schema": "./plugin-builder/schema.json",
"description": "Plugin Builder"
}
Expand Down
195 changes: 98 additions & 97 deletions builders/plugin-builder/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import {
BrowserBuilder,
NormalizedBrowserBuilderSchema
} from '@angular-devkit/build-angular';
import { Path, virtualFs } from '@angular-devkit/core';
import { BrowserBuilderOutput, executeBrowserBuilder, ExecutionTransformer } from '@angular-devkit/build-angular';
import { JsonObject } from '@angular-devkit/core';
import { createBuilder, BuilderContext } from '@angular-devkit/architect';
import * as fs from 'fs';
import * as webpack from 'webpack';
import { tap } from 'rxjs/operators';
import { Observable } from 'rxjs';

import { BuilderConfiguration, BuildEvent } from '@angular-devkit/architect';
import { tap } from 'rxjs/operators';

interface PluginBuilderSchema extends NormalizedBrowserBuilderSchema {
interface Options extends JsonObject {
/**
* A string of the form `path/to/file#exportName` that acts as a path to include to bundle
*/
Expand All @@ -25,109 +23,112 @@ interface PluginBuilderSchema extends NormalizedBrowserBuilderSchema {
*/
sharedLibs: string;
}
export default class PluginBuilder extends BrowserBuilder {
private options: PluginBuilderSchema;

private entryPointPath: string;
let entryPointPath;

function buildPlugin(options: Options,
context: BuilderContext,
transforms: {
webpackConfiguration?: ExecutionTransformer<webpack.Configuration>,
} = {}): Observable<BrowserBuilderOutput> {
options.deleteOutputPath = false;

validateOptions(options);

const originalWebpackConfigurationFn = transforms.webpackConfiguration;
transforms.webpackConfiguration = (config: webpack.Configuration) => {
patchWebpackConfig(config, options);

return originalWebpackConfigurationFn ? originalWebpackConfigurationFn(config) : config;
};

const result = executeBrowserBuilder(options as any, context, transforms);

return result.pipe(tap(() => {
patchEntryPoint('');
}));
}

function patchEntryPoint(contents: string) {
fs.writeFileSync(entryPointPath, contents);
}

function validateOptions(options: Options) {
const { pluginName, modulePath } = options;

if (!modulePath) {
throw Error('Please define modulePath!');
}

patchEntryPoint(contents: string) {
fs.writeFileSync(this.entryPointPath, contents);
if (!pluginName) {
throw Error('Please provide pluginName!');
}
}

buildWebpackConfig(
root: Path,
projectRoot: Path,
host: virtualFs.Host<fs.Stats>,
options: PluginBuilderSchema
) {
const { pluginName, sharedLibs } = this.options;

if (!this.options.modulePath) {
throw Error('Please define modulePath!');
}

if (!pluginName) {
throw Error('Please provide pluginName!');
}

const config = super.buildWebpackConfig(root, projectRoot, host, options);

// Make sure we are producing a single bundle
delete config.entry.polyfills;
delete config.optimization.runtimeChunk;
delete config.optimization.splitChunks;
delete config.entry.styles;

config.externals = {
rxjs: 'rxjs',
'@angular/core': 'ng.core',
'@angular/common': 'ng.common',
'@angular/forms': 'ng.forms',
'@angular/router': 'ng.router',
tslib: 'tslib'
// put here other common dependencies
};

if (sharedLibs) {
config.externals = [config.externals];
const sharedLibsArr = sharedLibs.split(',');
sharedLibsArr.forEach(sharedLibName => {
const factoryRegexp = new RegExp(`${sharedLibName}.ngfactory$`);
config.externals[0][sharedLibName] = sharedLibName; // define external for code
config.externals.push((context, request, callback) => {
if (factoryRegexp.test(request)) {
return callback(null, sharedLibName); // define external for factory
}
callback();
});
function patchWebpackConfig(config: webpack.Configuration, options: Options) {
const { pluginName, sharedLibs } = options;

// Make sure we are producing a single bundle
delete config.entry.polyfills;
delete config.entry['polyfills-es5'];
delete config.optimization.runtimeChunk;
delete config.optimization.splitChunks;
delete config.entry.styles;

config.externals = {
rxjs: 'rxjs',
'@angular/core': 'ng.core',
'@angular/common': 'ng.common',
'@angular/forms': 'ng.forms',
'@angular/router': 'ng.router',
tslib: 'tslib'
// put here other common dependencies
};

if (sharedLibs) {
config.externals = [config.externals];
const sharedLibsArr = sharedLibs.split(',');
sharedLibsArr.forEach(sharedLibName => {
const factoryRegexp = new RegExp(`${sharedLibName}.ngfactory$`);
config.externals[0][sharedLibName] = sharedLibName; // define external for code
config.externals.push((context, request, callback) => {
if (factoryRegexp.test(request)) {
return callback(null, sharedLibName); // define external for factory
}
callback();
});
}
});
}

const ngCompilerPluginInstance = config.plugins.find(
x => x.constructor && x.constructor.name === 'AngularCompilerPlugin'
);
if (ngCompilerPluginInstance) {
ngCompilerPluginInstance._entryModule = this.options.modulePath;
}
const ngCompilerPluginInstance = config.plugins.find(
x => x.constructor && x.constructor.name === 'AngularCompilerPlugin'
);
if (ngCompilerPluginInstance) {
ngCompilerPluginInstance._entryModule = options.modulePath;
}

// preserve path to entry point
// so that we can clear use it within `run` method to clear that file
this.entryPointPath = config.entry.main[0];
// preserve path to entry point
// so that we can clear use it within `run` method to clear that file
entryPointPath = config.entry.main[0];

const [modulePath, moduleName] = this.options.modulePath.split('#');
const [modulePath, moduleName] = options.modulePath.split('#');

const factoryPath = `${
modulePath.includes('.') ? modulePath : `${modulePath}/${modulePath}`
const factoryPath = `${
modulePath.includes('.') ? modulePath : `${modulePath}/${modulePath}`
}.ngfactory`;
const entryPointContents = `
const entryPointContents = `
export * from '${modulePath}';
export * from '${factoryPath}';
import { ${moduleName}NgFactory } from '${factoryPath}';
export default ${moduleName}NgFactory;
`;
this.patchEntryPoint(entryPointContents);
patchEntryPoint(entryPointContents);

config.output.filename = `${pluginName}.js`;
config.output.library = pluginName;
config.output.libraryTarget = 'umd';
// workaround to support bundle on nodejs
config.output.globalObject = `(typeof self !== 'undefined' ? self : this)`;

return config;
}

run(
builderConfig: BuilderConfiguration<PluginBuilderSchema>
): Observable<BuildEvent> {
this.options = builderConfig.options;
// I don't want to write it in my scripts every time so I keep it here
builderConfig.options.deleteOutputPath = false;

return super.run(builderConfig).pipe(
tap(() => {
// clear entry point so our main.ts is always empty
this.patchEntryPoint('');
})
);
}
config.output.filename = `${pluginName}.js`;
config.output.library = pluginName;
config.output.libraryTarget = 'umd';
// workaround to support bundle on nodejs
config.output.globalObject = `(typeof self !== 'undefined' ? self : this)`;
}

export default createBuilder<Options>(buildPlugin);
Loading