I am switching my SPA web app from Durandal to Aurelia and am now taking a look at the bundling process.
I use gulp to bundle the app and I followed the instructions on this Aurelia documentation page and other resources on the web. It works but there are some unclear things to me.
This is my gulpfile.js
var gulp = require('gulp'),
bundler = require('aurelia-bundler'),
uglify = require('gulp-uglify'),
htmlmin = require('gulp-htmlmin'),
del = require('del');
var config = {
force: true,
baseURL: '.', // baseURL of the application
configPath: './config.js', // config.js file. Must be within `baseURL`
bundles: {
"Scripts/build/app-build": { // bundle name/path. Must be within `baseURL`. Final path is: `baseURL/dist/app-build.js`.
includes: [
'[Scripts/app/**/*.js]',
'Scripts/app/**/*.html!text',
'Content/*.css!text'
],
options: {
inject: true,
minify: true,
depCache: true,
rev: true
}
},
"Scripts/build/vendor-build": {
includes: [
'jspm_packages/npm/babel-runtime#5.8.38/helpers/class-call-check.js',
'jspm_packages/npm/babel-runtime#5.8.38/helpers/create-class.js',
'jspm_packages/npm/babel-runtime#5.8.38/core-js/object/define-property.js',
'jspm_packages/npm/core-js#1.2.7/library/fn/object/define-property.js',
'jspm_packages/npm/babel-runtime#5.8.38/core-js/object/define-properties.js',
'jspm_packages/npm/core-js#1.2.7/library/fn/object/define-properties.js',
'npm:aurelia-framework#1.0.7',
'npm:aurelia-loader-default#1.0.0',
'npm:aurelia-logging-console#1.0.0',
'npm:aurelia-templating-binding#1.0.0',
'npm:aurelia-templating-resources#1.1.1',
'npm:aurelia-templating-router#1.0.0',
'npm:aurelia-knockout#1.0.2',
'npm:aurelia-history-browser#1.0.0',
'npm:aurelia-bootstrapper#1.0.0',
'npm:aurelia-fetch-client#1.0.1',
'npm:aurelia-router#1.0.6',
'npm:aurelia-animator-css#1.0.1',
'npm:babel-core#5.8.38',
'npm:babel-runtime#5.8.38',
'npm:core-js#1.2.7',
'github:systemjs/plugin-text#0.0.9'
],
options: {
inject: true,
minify: true,
depCache: true,
rev: true
}
}
}
};
gulp.task('build', ['minify'], function () {
return bundler.bundle(config);
});
And this is config.js
System.config({
baseURL: "/",
defaultJSExtensions: true,
transpiler: "babel",
babelOptions: {
"optional": [
"es7.decorators",
"es7.classProperties",
"runtime"
],
"compact": true
},
paths: {
"github:*": "jspm_packages/github/*",
"npm:*": "jspm_packages/npm/*"
},
bundles: {
},
map: //some mappings
}
If I run the gulp task to bundle, it works, and I can load my app using the bundled files, but there are some things I don't understand:
After the bundled files are created, the config.js file is updated within the "bundles:" property with the files which have been created for the bundle, and a random versioning number (since I had set 'rev: true' in the options)
bundles: [
"Scripts/build/vendor-build-4c2789cace.js": [
//list of files
]
]
When I run the task again, maybe after some changes, the new bundled file has been added to the config.js file like that:
bundles: [
"Scripts/build/vendor-build-4c2789cace.js": [
//list of files
],
"Scripts/build/vendor-build-t67uj8e5f4.js": [
//list of files
]
]
but as you can see, the old one is still there. How do I tell him to "clear" the bundles property when I create a new bundle?
Related
So I was trying to bundle in a bunch of external package dependencies using roll-up, like three JS and deck.gl. Right now I have a rollup config file set up like so, one to build just the code I have written and another that bundles in all the dependencies :
import externals from "rollup-plugin-node-externals";
export default [
{
input: "./Src/index.js",
output: [
{
file: "./Build/pgl.js",
format: "iife",
plugins: [
externals({
deps: true, // Dependencies will not be bundled in
}),
],
},
{
file: "./Build/pgl_module.js",
format: "iife",
plugins: [
externals({
deps: false, // Dependencies will be bundled in
}),
],
sourceMap: true,
},
],
},
];
I have also tried to do the same thing with something like
import { nodeResolve } from '#rollup/plugin-node-resolve';
export default {
input: 'src/index.js',
output: {
dir: 'output',
format: 'cjs'
},
plugins: [nodeResolve()]
};
but to no avail it never bundles up the node packages into the build file and always gives the error :
...
three/examples/jsm/lines/Line2.js (guessing 'Line2_js')
three/examples/jsm/lines/LineMaterial.js (guessing 'LineMaterial_js')
three/examples/jsm/lines/LineGeometry.js (guessing 'LineGeometry_js')
three/examples/jsm/controls/OrbitControls (guessing 'OrbitControls')
#deck.gl/core (guessing 'core')
#deck.gl/layers (guessing 'layers')
(!) Unresolved dependencies
I have a legacy app were we are trying to export some components with WP5 module-federation.
The plugin configuration is currently like this:
output: {
filename: "[name].bundle.js",
},
plugins: [
new ModuleFederationPlugin({
name: "remote",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./TestExport": path.resolve(__dirname, "../src/TestExport"),
},
shared: {
"react#17": {
singleton: true,
requiredVersion: deps.react,
},
},
}
),
]
the webpack config file is in a config dir in the root folder, this is why we are exposing the component with path.resolve.
When compiling the code, the remoteEntry.js file is not generated and we can't import it in another application using module-federation.
I've tried creating an app and exporting something with module-federation and consuming it inside the legacy app and it worked, but the opposite doesn't work.
I want to set up an Angular 1.x app from scratch using webpack 2.
I am having trouble finding the best configuration for webpack.config, with optimal entry and output for production (meaning, all code, style and templating minified and gziped with no code repetition).
My main problem is how to set up webpack.config so that it recognizes all partials within the folder structure of my project, like these:
My current config file, for reference (which can't see subfolders):
var HtmlWebpackPlugin = require( 'html-webpack-plugin' );
var ExtractTextPlugin = require( 'extract-text-webpack-plugin' );
var path = require( 'path' );
module.exports = {
devServer: {
compress: true,
contentBase: path.join( __dirname, '/dist' ),
open: true,
port: 9000,
stats: 'errors-only'
},
entry: './src/app.js',
output: {
path: path.join( __dirname, '/dist' ),
filename: 'app.bundle.js'
},
module: {
rules: [ {
test: /\.scss$/,
use: ExtractTextPlugin.extract( {
fallback: 'style-loader',
use: [
'css-loader',
'sass-loader'
],
publicPath: '/dist'
} )
} ]
},
plugins: [
new HtmlWebpackPlugin( {
hash: true,
minify: { collapseWhitespace: true },
template: './src/index.html',
title: 'Prov'
} ),
new ExtractTextPlugin( {
filename: 'main.css',
allChunks: true
} )
]
};
Note that this isn't an exhaustive solution, as there are many optimizations one can make in the frontend, and I've kept the code snippets fairly short.
With webpack, there are a few routes that you can take to include partials into your app.js.
Solution 1
You can import/require your partials within app.js as such:
app.js
var angular = require('angular');
var proverbList = require('./proverb/list/proverb.list');
// require other components
// set up your app as normal
This allows the app.bundle.js to include your component js files in the main bundle. You can also use html-loader to include templates in the final bundle.
This isn't ideal, as all it does is create a large bundle.js (which doesn't leverage multiple downloads with http2 nor does it allow loading of components/files when the user explicitly requires it).
Solution 2
Importing partials as separate entry files into your webpack bundle:
webpack.config.js
const globby = require('globby');
const sourceDir = 'src';
var webpackentry = {
app: `${__dirname}/src/app.js`
};
const glob = globby.sync(`${__dirname}/${sourceDir}/**/*.js`)
.map((file)=>{
let name = file.split('/').pop().replace('.js', '');
webpackentry[name] = file;
});
const config = {
entry: webpackentry,
...
}
The second solution is unorthodox but it can be useful if you wanted to split all your partials as <script> tags in your html (for example if your company/team uses that as a means to include your directive/components/controllers), or if you have an app-2.bundle.js.
Solution 3
Use CommonsChunkPlugin:
webpack.config.js
let webpackentry = {
vendor: [
'module1',
'module2',
'module3',
]
}
...
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor'] //... add other modules
})
]
CommonsChunkPlugin allows webpack to scrawl through your entry files and discern common modules that are shared among them. This means that even if you are importing module1 in different files, they will be compiled only once in your final bundle.
I created an Aurelia app using the Aurelia CLI (au new) and would like to set up code coverage (preferably with karma-coverage, but if that's not possible I'll use whatever).
I first npm install karma-coverage --save-dev then copy the test.js task over to a cover.js (so that I can run au cover).
cover.js
import {Server as Karma} from 'karma';
import {CLIOptions} from 'aurelia-cli';
// import project from "../aurelia.json";
export function cover(done) {
new Karma({
// This is the same as what's in karma.conf.js after running
// Except I added the 'src\\**\\*.js' part
files: [
'scripts\\vendor-bundle.js',
{pattern: 'test\\unit\\**\\*.js', included: false},
'test/aurelia-karma.js',
'scripts\\app-bundle.js',
'scripts\\materialize-bundle.js',
{pattern: 'src\\**\\*.js', included: false}
],
configFile: __dirname + '/../../karma.conf.js',
singleRun: !CLIOptions.hasFlag('watch'),
reporters: ['progress', 'coverage'],
//logLevel: 'debug',
preprocessors: {
// [project.unitTestRunner.source]: [project.transpiler.id], // Is this actually needed? Nothing changes if I add or remove this...
'src/**/*.js': ['babel', 'coverage']
},
coverageReporter: {
includeAllSources: true,
reporters: [
{type: 'html', dir: 'coverage'},
{type: 'text'}
]
}
}, done).start();
}
export default cover;
This... gets me somewhere?
But I don't think the tests are being linked to the individual src files (they're instead being linked to app-bundle.js).
Is there any way to get code coverage at the src file level (i.e. not bundle level) for an Aurelia app?
Other Files of Interest
app.js
export class App {
constructor() {
this.message = 'Hello World!';
}
}
karma.conf.js
"use strict";
const path = require('path');
const project = require('./aurelia_project/aurelia.json');
let testSrc = [
{ pattern: project.unitTestRunner.source, included: false },
'test/aurelia-karma.js'
];
let output = project.platform.output;
let appSrc = project.build.bundles.map(x => path.join(output, x.name));
let entryIndex = appSrc.indexOf(path.join(output, project.build.loader.configTarget));
let entryBundle = appSrc.splice(entryIndex, 1)[0];
let files = [entryBundle].concat(testSrc).concat(appSrc); console.log(files);
module.exports = function(config) {
config.set({
basePath: '',
frameworks: [project.testFramework.id],
files: files,
exclude: [],
preprocessors: {
[project.unitTestRunner.source]: [project.transpiler.id]
},
'babelPreprocessor': { options: project.transpiler.options },
reporters: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
// client.args must be a array of string.
// Leave 'aurelia-root', project.paths.root in this order so we can find
// the root of the aurelia project.
client: {
args: ['aurelia-root', project.paths.root]
}
});
};
From your terminal screenshot, it looks like you're getting coverage on your individual files src/app.js (12.5%), src/environment.js (0%) and src/main.js (12.5%) in addition to the bundle file. It has to do with that extra line you added including the files that are in src/.
I suspect that if you go to your coverage directory and view that in the browser, you'll see more detailed results.
I'm not sure about the Aurelia app specifically, but to get individual file coverage, you want to have your tests run against your original files instead of the bundled version and you want that same path to your original files to also be in preprocessors.
files: [
...
'src/**/*.js',
'path/to/tests/*.spec.js'
...
],
preprocessors: {
'src/**/*.js': ['coverage']
}
This pattern (which you've done) is what tells Karma to load the individual files, and include them in the coverage. At this point, I wouldn't bother loading the bundled application code since you have all the raw stuff being loaded.
Also be sure to load them in the order they're probably bundled in so that you can be sure Karma can initialize the app in the first place.
files: [
'src/app.js',
'src/environment.js',
'src/main.js'
]
And if you are putting your specs named myTest.spec.js next to the files they test, you can use src/!(*.spec).js to make sure that you are including application code without the tests.
I finally got it working, but it required a lot of under-the-hood modifications =\
First I had to install the following packages
npm install karma-coverage --save-dev
npm install karma-requirejs --save-dev
npm install babel-plugin-istanbul --save-dev
aurelia_project/tasks/cover.js
This is based on test.js.
Add requirejs to the list of frameworks (from the karma-requirejs package)
Add the individual src/test files (with included: false) as well as test/aurelia-karma-cover.js, which does the actual requireing of test files.
Modified aurelia-karma.js into aurelia-karma-cover.js to not include /test/unit/setup.js (it was giving me trouble with aurelia-browser-pal dependencies)
Remove "coverage" from src file preprocessing (babel-plugin-istanbul will now handle instrumentation of code - see statement at the end for details).
import {Server as Karma} from 'karma';
import {CLIOptions} from 'aurelia-cli';
import project from "../aurelia.json";
export function cover(done) {
new Karma({
configFile: __dirname + '/../../karma.conf.js',
frameworks: [project.testFramework.id, 'requirejs'],
files: [
{pattern: 'src\\**\\*.js', included: false},
{pattern: 'test\\unit\\**\\*.js', included: false},
// This file actually loads the spec files via require - it's based on aurelia-karma.js
// but removes setup.js and its dependencies
'test/aurelia-karma-cover.js'
],
exclude: [
'src/environment.js',
'src/main.js',
'src/resources/index.js'
],
preprocessors: {
'src/**/*.js': ['babel'],
},
reporters: ['progress', 'coverage'],
singleRun: !CLIOptions.hasFlag('watch'),
coverageReporter: {
includeAllSources: true,
reporters: [
{type: 'html', dir: 'coverage'},
{type: 'text'}
]
}
}, done).start();
}
export default cover;
test/unit/aurelia-karma-cover.js
Just change var allTestFiles = ['/base/test/unit/setup.js']; to var allTestFiles = []; to avoid aurelia-pal-browser dependency errors.
aurelia_project/aurelia.json
Add "istanbul" to the transpiler.options.plugins list if using babel-plugin-istanbul.
* Without babel-plugin-istanbul, code coverage works on post-transpiled code, which adds boilerplate that can't really be tested. This allows you to get to 100% code coverage ;)
#lebolo These were the adjustments I had to make to get things working here:
aurelia_project/tasks/cover.js
Included a constant to vendors path in the beginning of cover function:
const VENDORS_PATH = __dirname + '/../../node_modules/';
Used VENDORS_PATH to include libs I depend on, in files:
files: [
{pattern: VENDORS_PATH + 'moment/min/moment.min.js', included: false},
{pattern: 'src/**/*.js', included: false},
{pattern: 'test/unit/**/*.js', included: false},
'test/aurelia-karma-cover.js'
]
Included test files in preprocessors:
preprocessors: {
'src/**/*.js': ['babel'],
'test/unit/**/*.js': ['babel']
}
test/unit/aurelia-karma-cover.js
Change the way url is built in requirejs.load function:
url = '/base/' + url; => url = ['/base', root, url].join('/');
Included one more function to config requirejs paths and calling it after patchRequireJS() call:
function configRequire() {
var VENDORS_PATH = '../node_modules/';
requirejs.config({
paths: {
'moment': VENDORS_PATH + 'moment/min/moment.min'
}
});
}
I'm working on a project using requirejs and I'm trying to optimize my javascript into two files: vendor libraries and app code. Unfortunately, I can't seem to get it to work.
Folder structure:
backbone_components/
app/
less/
js/
component1/
component2/
layouts/
dashboard/
about/
lib/
config.js
main.js
index.html
dist/
Gruntfile.js
config.js:
require.config({
baseUrl: '../js/',
paths: {
jquery: '../../bower_components/jquery/dist/jquery',
underscore: '../../bower_components/lodash/dist/lodash',
backbone: '../../bower_components/backbone/backbone',
text: '../../bower_components/requirejs-text/text'
},
enforeceDefine: true
});
define(['backbone', 'main'], function(Backbone, app) {
});
Gruntfile.js:
requirejs: {
options: {
dir: 'dist/js/',
mainConfigFile: 'app/js/config.js',
optimize: 'none',
normalizeDirDefines: 'all',
skipDirOptimize: true
},
dist: {
options: {
modules: [
{
name: 'vendor',
include: ['jquery', 'underscore', 'backbone', 'text'],
},
{
name: 'app',
exclude: ['vendor']
}
]
}
}
});
When I run grunt, I get the following error: "Error: Error: ERROR: module path does not exist: /path/to/project/app/js/app/js/vendor.js for module named: vendor."
Why is it looking for "vendor" when it doesn't exist yet?
I take it that you have no module named vendor in the source you want to optimize. If this is correct, then the error you are getting is because r.js is looking for a module named vendor. You have to tell it to create it with the create option:
modules: [
{
name: 'vendor',
create: true, // <--- Add this option!
include: ['jquery', 'underscore', 'backbone', 'text']
},
{
name: 'app',
exclude: ['vendor']
}
]
Without the option r.js understands you to be effectively saying "take the vendor module and include with it jquery, etc." With the option, you are telling it to create vendor from scratch.
The create option is documented in this file.