diff --git a/src/bazel/BUILD.bazel b/src/bazel/BUILD.bazel new file mode 100644 index 0000000000..551eedf4c2 --- /dev/null +++ b/src/bazel/BUILD.bazel @@ -0,0 +1,8 @@ +load("@build_bazel_rules_typescript//:defs.bzl", "nodejs_binary") +exports_files(["webpack.config.js", "test.js"]) + +nodejs_binary( + name = "webpack", + entry_point = "webpack/bin/webpack", + visibility = ["//visibility:public"], +) diff --git a/src/bazel/WORKSPACE b/src/bazel/WORKSPACE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/bazel/karma.conf.js b/src/bazel/karma.conf.js new file mode 100644 index 0000000000..9a7363e03c --- /dev/null +++ b/src/bazel/karma.conf.js @@ -0,0 +1,109 @@ +/** + * Warning: the testing rule will change. + * + * Instead of running karma outside of bazel against the bin_dir directory, we will run it as part of the bazel process. + */ +module.exports = function(config) { + const webpackConfig = { + resolveLoader: { + alias: { + "template-loader": '@nrwl/nx/bazel/template-loader' + } + }, + module: { + rules: [ + { + test: /\.component\.js$/, + use: [ + {loader: 'template-loader' } + ] + }, + { + test: /\.html$/, + use: [ + {loader: 'raw-loader' } + ] + }, + { + test: /\.css$/, + use: [ + {loader: 'raw-loader' } + ] + } + ] + } + }; + + config.set({ + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: `${config.opts.bin_dir}/${config.opts.app}`, + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + // list of files / patterns to load in the browser + files: [ + { pattern: 'test.js', watched: false} + ], + + // list of files to exclude + exclude: [], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + 'test.js': ['webpack'] + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: config.opts.reporters ? config.opts.reporters : (config.opts.progress ? ['progress'] : ['dots']), + + webpack: webpackConfig, + + webpackMiddleware: { + stats: 'errors-only' + }, + + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('karma-webpack') + ], + + coverageIstanbulReporter: { + reports: [ 'html', 'lcovonly' ], + fixWebpackSourcePaths: true + }, + + client: { + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + + // web server port + port: config.opts.port ? config.opts.port : 9876, + + // enable / disable colors in the output (reporters and logs) + colors: config.opts.colors, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.opts.log ? config.opts.log: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + // browsers: ['PhantomJS'], + browsers: ['Chrome'], + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity + }); +}; diff --git a/src/bazel/template-loader.js b/src/bazel/template-loader.js new file mode 100644 index 0000000000..3fb916e2bf --- /dev/null +++ b/src/bazel/template-loader.js @@ -0,0 +1,30 @@ +var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*([,}]))/gm; +var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g; +var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g; + +function replaceStringsWithRequires(string) { + return string.replace(stringRegex, function (match, quote, url) { + if (url.charAt(0) !== ".") { + url = "./" + url; + } + return "require('" + url + "')"; + }); +} + +module.exports = function(source, sourcemap) { + // Not cacheable during unit tests; + this.cacheable && this.cacheable(); + + var newSource = source.replace(templateUrlRegex, function (match, url) { + return "template:" + replaceStringsWithRequires(url); + }).replace(stylesRegex, function (match, urls) { + return "styles:" + replaceStringsWithRequires(urls); + }); + + // Support for tests + if (this.callback) { + this.callback(null, newSource, sourcemap) + } else { + return newSource; + } +}; diff --git a/src/bazel/webpack.bzl b/src/bazel/webpack.bzl new file mode 100644 index 0000000000..99706b7268 --- /dev/null +++ b/src/bazel/webpack.bzl @@ -0,0 +1,62 @@ +def _collect_es5_sources_impl(target, ctx): + result = set() + if hasattr(ctx.rule.attr, "srcs"): + for dep in ctx.rule.attr.srcs: + if hasattr(dep, "es5_sources"): + result += dep.es5_sources + if hasattr(target, "typescript"): + result += target.typescript.es5_sources + return struct(es5_sources = result) + +_collect_es5_sources = aspect( + _collect_es5_sources_impl, + attr_aspects = ["deps", "srcs"], +) + +def _webpack_bundle_impl(ctx): + inputs = set() + for s in ctx.attr.srcs: + if hasattr(s, "es5_sources"): + inputs += s.es5_sources + + config = ctx.files.config + + if ctx.attr.mode == 'prod': + main = ctx.new_file('bundles/main.bundle.prod.js') + polyfills = ctx.new_file('bundles/polyfills.bundle.prod.js') + vendor = ctx.new_file('bundles/vendor.bundle.prod.js') + styles = ctx.new_file('bundles/styles.bundle.prod.js') + else: + main = ctx.new_file('bundles/main.bundle.js') + polyfills = ctx.new_file('bundles/polyfills.bundle.js') + vendor = ctx.new_file('bundles/vendor.bundle.js') + styles = ctx.new_file('bundles/styles.bundle.js') + + inputs += [config] + args = [] + + if ctx.attr.mode == 'prod': + args += ['-p'] + + args += ['--config', config.path] + args += ['--env.bin_dir', ctx.configuration.bin_dir.path] + args += ['--env.package', ctx.label.package] + args += ['--env.mode', ctx.attr.mode] + + ctx.action( + progress_message = "Webpack bundling %s" % ctx.label, + inputs = inputs.to_list(), + outputs = [main, polyfills, vendor, styles], + executable = ctx.executable._webpack, + arguments = args, + ) + return DefaultInfo(files=depset([main, polyfills, vendor, styles])) + +webpack_bundle = rule(implementation = _webpack_bundle_impl, + attrs = { + "srcs": attr.label_list(allow_files=True, aspects=[_collect_es5_sources]), + "config": attr.label(allow_single_file=True, mandatory=True), + "mode": attr.string(default="dev"), + "_webpack": attr.label(default=Label("@nrwl//:webpack"), executable=True, cfg="host") + } +) diff --git a/src/bazel/webpack.config.js b/src/bazel/webpack.config.js new file mode 100644 index 0000000000..4295e05ed5 --- /dev/null +++ b/src/bazel/webpack.config.js @@ -0,0 +1,185 @@ +const fs = require('fs'); +const path = require('path'); +const ProgressPlugin = require('webpack/lib/ProgressPlugin'); +const CircularDependencyPlugin = require('circular-dependency-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +const { NoEmitOnErrorsPlugin, SourceMapDevToolPlugin, NamedModulesPlugin } = require('webpack'); +const { GlobCopyWebpackPlugin, NamedLazyChunksWebpackPlugin, BaseHrefWebpackPlugin } = require('@angular/cli/plugins/webpack'); +const { CommonsChunkPlugin } = require('webpack').optimize; + +const nodeModules = path.join(process.cwd(), 'node_modules'); +const realNodeModules = fs.realpathSync(nodeModules); + +const entryPoints = ["inline", "polyfills", "styles", "vendor", "main"]; +const baseHref = ""; + +module.exports = function(env) { + const name = path.parse(env.package).name; + const apps = JSON.parse(fs.readFileSync(path.join(process.cwd(), '.angular-cli.json'), 'UTF-8')).apps; + const appConfig = apps.filter(a => a.name === name)[0]; + const out = path.join(process.cwd(), env.bin, env.package, 'bundles'); + const src = path.join(process.cwd(), env.bin, appConfig.root); + + // victor todo: remove it when ng_module rule is fixed + const alias = Object.assign({}, { + '@angular/core/core': '@angular/core/@angular/core.es5', + '@angular/common/common': '@angular/common/@angular/common.es5', + '@angular/platform-browser/platform-browser': '@angular/platform-browser/@angular/platform-browser.es5' + }); + + return { + "resolve": { + "extensions": [ + ".js" + ], + "modules": [ + "./node_modules" + ], + "symlinks": true, + alias + }, + "resolveLoader": { + "modules": [ + "./node_modules" + ] + }, + "entry": { + "main": [ + tsToJs(path.join(src, appConfig.main)) + ], + "polyfills": [ + tsToJs(path.join(src, appConfig.polyfills)) + ], + "styles": appConfig.styles.map(s => path.join(src, s)) + }, + "output": { + "path": out, + "filename": "[name].bundle.js", + "chunkFilename": "[id].chunk.js" + }, + "module": { + "rules": [ + { + "enforce": "pre", + "test": /\.js$/, + "loader": "source-map-loader", + "exclude": [ + /(\\|\/)node_modules(\\|\/)/ + ] + }, + { + "test": /\.html$/, + "loader": "raw-loader" + }, + { + "test": /\.(eot|svg|cur)$/, + "loader": "file-loader?name=[name].[hash:20].[ext]" + }, + { + "test": /\.(jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/, + "loader": "url-loader?name=[name].[hash:20].[ext]&limit=10000" + } + ] + }, + "plugins": [ + new NoEmitOnErrorsPlugin(), + new GlobCopyWebpackPlugin({ + "patterns": [ + "assets", + "favicon.ico" + ], + "globOptions": { + "cwd": src, + "dot": true, + "ignore": "**/.gitkeep" + } + }), + new ProgressPlugin(), + new CircularDependencyPlugin({ + "exclude": /(\\|\/)node_modules(\\|\/)/, + "failOnError": false + }), + new NamedLazyChunksWebpackPlugin(), + new HtmlWebpackPlugin({ + "template": path.join(src, 'index.html'), + "filename": "./index.html", + "hash": false, + "inject": true, + "compile": true, + "favicon": false, + "minify": false, + "cache": true, + "showErrors": true, + "chunks": "all", + "excludeChunks": [], + "xhtml": true, + "chunksSortMode": function sort(left, right) { + let leftIndex = entryPoints.indexOf(left.names[0]); + let rightindex = entryPoints.indexOf(right.names[0]); + if (leftIndex > rightindex) { + return 1; + } + else if (leftIndex < rightindex) { + return -1; + } + else { + return 0; + } + } + }), + new BaseHrefWebpackPlugin({}), + new CommonsChunkPlugin({ + "name": [ + "inline" + ], + "minChunks": null + }), + new CommonsChunkPlugin({ + "name": [ + "vendor" + ], + "minChunks": (module) => { + return module.resource + && (module.resource.startsWith(nodeModules) + || module.resource.startsWith(realNodeModules)); + }, + "chunks": [ + "main" + ] + }), + new SourceMapDevToolPlugin({ + "filename": "[file].map[query]", + "moduleFilenameTemplate": "[resource-path]", + "fallbackModuleFilenameTemplate": "[resource-path]?[hash]", + "sourceRoot": "webpack:///" + }), + new CommonsChunkPlugin({ + "name": [ + "main" + ], + "minChunks": 2, + "async": "common" + }), + new NamedModulesPlugin({}), + ], + "node": { + "fs": "empty", + "global": true, + "crypto": "empty", + "tls": "empty", + "net": "empty", + "process": true, + "module": false, + "clearImmediate": false, + "setImmediate": false + }, + "devServer": { + "historyApiFallback": true + } + }; +}; + +function tsToJs(s) { + return `${s.substring(0, s.length - 3)}.js`; +} diff --git a/src/schematics/app/files/__directory__/__name__/BUILD.bazel__tmpl__ b/src/schematics/app/files/__directory__/__name__/BUILD.bazel__tmpl__ index 1ac700b16c..b9cd0ba596 100644 --- a/src/schematics/app/files/__directory__/__name__/BUILD.bazel__tmpl__ +++ b/src/schematics/app/files/__directory__/__name__/BUILD.bazel__tmpl__ @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) load("@build_bazel_rules_angular//:defs.bzl", "ng_module") -load("@build_bazel_rules_nrwl//:webpack.bzl", "webpack_bundle") +load("@nrwl//:webpack.bzl", "webpack_bundle") ng_module( name = "compile", @@ -9,6 +9,7 @@ ng_module( tsconfig = "//:tsconfig.json" ) +# temporary work-around to handle static genrule( name = "copy_static", srcs = ["src"] + glob(["src/**/*"], exclude=["**/*.ts"]), @@ -31,5 +32,5 @@ filegroup( webpack_bundle( name = "<%= name %>", srcs = ["compile_and_static"], - config = "@build_bazel_rules_nrwl//:webpack.config.js" + config = "@nrwl//:webpack.config.js" ) diff --git a/src/schematics/app/index.ts b/src/schematics/app/index.ts index 40ef8cf7a9..c17d0a791c 100644 --- a/src/schematics/app/index.ts +++ b/src/schematics/app/index.ts @@ -9,7 +9,7 @@ import {insert} from '../utility/ast-utils'; function addBootstrap(path: string): Rule { return (host: Tree) => { const modulePath = `${path}/app/app.module.ts`; - const moduleSource = host.read(modulePath) !.toString('utf-8'); + const moduleSource = host.read(modulePath)!.toString('utf-8'); const sourceFile = ts.createSourceFile(modulePath, moduleSource, ts.ScriptTarget.Latest, true); const importChanges = addImportToModule(sourceFile, modulePath, 'BrowserModule', '@angular/platform-browser'); const bootstrapChanges = addBootstrapToModule(sourceFile, modulePath, 'AppComponent', './app.component'); diff --git a/src/schematics/workspace/files/BUILD.bazel__tmpl__ b/src/schematics/workspace/files/BUILD.bazel__tmpl__ index 60a4458d8b..ac93f97322 100644 --- a/src/schematics/workspace/files/BUILD.bazel__tmpl__ +++ b/src/schematics/workspace/files/BUILD.bazel__tmpl__ @@ -3,6 +3,7 @@ exports_files(["tsconfig.json"]) load("@build_bazel_rules_angular//:defs.bzl", "ng_external_libraries") +# change to glob(["node_modules/**/*.js", "node_modules/**/*.json", "node_modules/**/*.d.ts"]) filegroup(name = "node_modules", srcs = glob([ # should not be whitelisted "node_modules/@angular/**", @@ -20,14 +21,13 @@ filegroup(name = "node_modules", srcs = glob([ "node_modules/webpack/**" ], exclude=["node_modules/@angular/cli/**"])) +# this should go away soon when we do bootstrap codegen in the scripts.postinstall ng_external_libraries(name = "ng_libs", srcs = glob([ "node_modules/@angular/**" ], exclude = [ "node_modules/@angular/cli/**", "node_modules/@angular/platform-browser/animations*", "node_modules/@angular/platform-browser/animations/**", - - # Alex E? "node_modules/@angular/router*", "node_modules/@angular/router/**", ])) diff --git a/src/schematics/workspace/files/README.md__tmpl__ b/src/schematics/workspace/files/README.md__tmpl__ index 313785e797..7c92402a14 100755 --- a/src/schematics/workspace/files/README.md__tmpl__ +++ b/src/schematics/workspace/files/README.md__tmpl__ @@ -1,6 +1,6 @@ # <%= className %> -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version <%= version %>. +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version EXPERIMENTAL BAZEL. ## Development server