babel-plugin-ng-hot-reload
Advanced tools
Comparing version 2.1.0-alpha003 to 2.1.0-alpha004
@@ -7,4 +7,5 @@ import * as Babel from '@babel/core'; | ||
preserveState: boolean; | ||
angularReference: string; | ||
}; | ||
export default function (babel: BabelT, { angularGlobal, forceRefresh, preserveState }: PluginOptions): { | ||
export default function (babel: BabelT, { angularGlobal, forceRefresh, preserveState, angularReference, }: PluginOptions): { | ||
name: string; | ||
@@ -11,0 +12,0 @@ post(): void; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
function default_1(babel, { angularGlobal = false, forceRefresh = true, preserveState = true }) { | ||
function default_1(babel, { angularGlobal = false, forceRefresh = true, preserveState = true, angularReference = `require('angular'), angular`, }) { | ||
const { types: t, template } = babel; | ||
@@ -8,12 +8,14 @@ let state = { | ||
topLevelExports: new Map(), | ||
angularUseDetected: false, | ||
pathsToRemove: new Set(), | ||
pathsToReplace: new Map(), | ||
}; | ||
const registerAngularUse = new Map(); | ||
const corePath = 'ng-hot-reload-core'; | ||
const requireAngular = '(require("angular"), angular)'; | ||
const EXPORTS_PREFIX = '__ngHotReload_'; | ||
const INNER_EXPORT_VARIBALE = '__ngHotReload_exports__'; | ||
const INNER_EXPORT_VARIABLE = '__ngHotReload_exports__'; | ||
const ANGULAR_PACKAGE_NAME = 'angular'; | ||
const buildHotReloadTemplate = template(` | ||
/* babel-plugin-ng-hot-reload */ | ||
const %%extractedExports%% = (function(__ngHotReloadLoaderAngularGlobal) { | ||
var ${INNER_EXPORT_VARIBALE}; | ||
var ${INNER_EXPORT_VARIABLE}; | ||
var angular = module.hot ? (function() { | ||
@@ -29,3 +31,3 @@ var loader = require(${JSON.stringify(corePath)}); | ||
try { | ||
${INNER_EXPORT_VARIBALE} = (function() { | ||
${INNER_EXPORT_VARIABLE} = (function() { | ||
/* babel-plugin-ng-hot-reload end*/ | ||
@@ -47,152 +49,175 @@ %%source%% | ||
} | ||
return ${INNER_EXPORT_VARIBALE}; | ||
})(${requireAngular}); | ||
return ${INNER_EXPORT_VARIABLE}; | ||
})(${angularReference}); | ||
/* babel-plugin-ng-hot-reload end */ | ||
`); | ||
return { | ||
name: 'ng-hot-reload', | ||
post() { | ||
// Clear the storage after each file | ||
state.topLevelImports.clear(); | ||
state.topLevelExports.clear(); | ||
state.angularUseDetected = false; | ||
const visitor = { | ||
Program: { | ||
exit(path) { | ||
// Only apply the hot-module-replacement template when usage of angular | ||
// is detected | ||
if (!registerAngularUse.has(path)) { | ||
return; | ||
} | ||
const { node: { body: sourceBody }, } = path; | ||
// Apply all transformations | ||
state.pathsToRemove.forEach((pathToRemove) => pathToRemove.remove()); | ||
state.pathsToReplace.forEach((replacer, pathToReplace) => pathToReplace.replaceWith(replacer)); | ||
// Adds a return statement to the inner wrapper function which | ||
// contains the exports from the module | ||
// Also adds an destructor to the outside of the wrapper to make the | ||
// exports from inside the wrapper avaiable in global scope | ||
// | ||
// export default Controller; | ||
// export const namedExport | ||
// | ||
// == becomes == | ||
// | ||
// Appended to wrapped source: | ||
// -- | ||
// return { | ||
// __export_default: Controller, | ||
// __export_namedExport: namedExport | ||
// } | ||
// | ||
// Added to outer wrapper: | ||
// -- | ||
// const { __export_default, __export_namedExport } = (function() {...})(); | ||
// | ||
// Appended to template: | ||
// -- | ||
// export { | ||
// __export_default as default, | ||
// __export_namedExport as namedExport | ||
// }; | ||
const moduleExports = []; | ||
const extractedExports = []; | ||
const topLevelExports = []; | ||
state.topLevelExports.forEach((value, key) => { | ||
const identifierKey = `${EXPORTS_PREFIX}${key}`; | ||
// Properties of the return statement | ||
moduleExports.push(t.objectProperty(t.identifier(identifierKey), value)); | ||
// Properties for the outer const destrcutor | ||
extractedExports.push(t.objectProperty(t.identifier(identifierKey), t.identifier(identifierKey), false, true)); | ||
// Restore the topLevelexports | ||
topLevelExports.push(t.exportSpecifier(t.identifier(identifierKey), t.identifier(key))); | ||
}); | ||
// Wrap the properties in return statement | ||
const exportsAsReturnStatement = t.returnStatement(t.objectExpression(moduleExports)); | ||
// build the template | ||
const hotReloadTemplateAst = buildHotReloadTemplate({ | ||
source: sourceBody, | ||
exports: exportsAsReturnStatement, | ||
extractedExports: t.objectPattern(extractedExports), | ||
}); | ||
const finalBody = [ | ||
...state.topLevelImports.values(), | ||
hotReloadTemplateAst, | ||
topLevelExports.length > 0 ? t.exportNamedDeclaration(null, topLevelExports) : undefined, | ||
].filter(Boolean); | ||
path.node.body = finalBody; | ||
}, | ||
}, | ||
visitor: { | ||
Program: { | ||
exit(path) { | ||
const { node: { body: sourceBody }, } = path; | ||
// Adds a return statement to the inner wrapper function which | ||
// contains the exports from the module | ||
// Also adds an destructor to the outside of the wrapper to make the | ||
// exports from inside the wrapper avaiable in global scope | ||
// | ||
// export default Controller; | ||
// export const namedExport | ||
// | ||
// == becomes == | ||
// | ||
// Appended to wrapped source: | ||
// -- | ||
// return { | ||
// __export_default: Controller, | ||
// __export_namedExport: namedExport | ||
// } | ||
// | ||
// Added to outer wrapper: | ||
// -- | ||
// const { __export_default, __export_namedExport } = (function() {...})(); | ||
// | ||
// Appended to template: | ||
// -- | ||
// export { | ||
// __export_default as default, | ||
// __export_namedExport as namedExport | ||
// }; | ||
const moduleExports = []; | ||
const extractedExports = []; | ||
const topLevelExports = []; | ||
state.topLevelExports.forEach((value, key) => { | ||
const identifierKey = `${EXPORTS_PREFIX}${key}`; | ||
// Properties of the return statement | ||
moduleExports.push(t.objectProperty(t.identifier(identifierKey), value)); | ||
// Properties for the outer const destrcutor | ||
extractedExports.push(t.objectProperty(t.identifier(identifierKey), t.identifier(identifierKey), false, true)); | ||
// Restore the topLevelexports | ||
topLevelExports.push(t.exportSpecifier(t.identifier(identifierKey), t.identifier(key))); | ||
ImportDeclaration(path) { | ||
const { node } = path; | ||
// Check if the import is angular | ||
if (node.source.value === ANGULAR_PACKAGE_NAME) { | ||
const parentProgram = path.findParent(path => path.isProgram()); | ||
registerAngularUse.set(parentProgram, true); | ||
} | ||
// Add import to the list and remove it for now | ||
state.topLevelImports.add(node); | ||
state.pathsToRemove.add(path); | ||
}, | ||
ExportNamedDeclaration(path) { | ||
const declaration = path.get('declaration'); | ||
if (declaration.node !== null) { | ||
if (declaration.type === 'VariableDeclaration') { | ||
// Export variable declaration | ||
// e.g: | ||
// export const foo = 'bar', | ||
// bar = 'foo'; | ||
const { declarations } = declaration.node; | ||
declarations.forEach(declaration => { | ||
const identifier = declaration.id; | ||
state.topLevelExports.set(identifier.name, identifier); | ||
}); | ||
// Wrap the properties in return statement | ||
const exportsAsReturnStatement = t.returnStatement(t.objectExpression(moduleExports)); | ||
// build the template | ||
const hotReloadTemplateAst = buildHotReloadTemplate({ | ||
source: sourceBody, | ||
exports: exportsAsReturnStatement, | ||
extractedExports: t.objectPattern(extractedExports), | ||
}); | ||
const finalBody = [ | ||
...state.topLevelImports.values(), | ||
hotReloadTemplateAst, | ||
topLevelExports.length > 0 | ||
? t.exportNamedDeclaration(null, topLevelExports) | ||
: undefined, | ||
].filter(Boolean); | ||
path.node.body = finalBody; | ||
}, | ||
}, | ||
ImportDeclaration(path) { | ||
const node = path.node; | ||
// Add it to the list and remove it from the code | ||
state.topLevelImports.add(node); | ||
path.remove(); | ||
}, | ||
ExportNamedDeclaration(path) { | ||
const declaration = path.get('declaration'); | ||
if (declaration.node !== null) { | ||
if (declaration.type === 'VariableDeclaration') { | ||
// Export variable declaration | ||
// e.g: | ||
// export const foo = 'bar', | ||
// bar = 'foo'; | ||
const { declarations } = declaration.node; | ||
declarations.forEach(declaration => { | ||
const identifier = declaration.id; | ||
state.topLevelExports.set(identifier.name, identifier); | ||
}); | ||
} | ||
else { | ||
// Export right before declaration | ||
// e.g: | ||
// export class Foo {}; | ||
const identifier = declaration.get('id').node; | ||
state.topLevelExports.set(identifier.name, identifier); | ||
} | ||
// Replace the export declaration with the actual declaration | ||
path.replaceWith(declaration); | ||
} | ||
else { | ||
// Export specifier | ||
// Export right before declaration | ||
// e.g: | ||
// const foo = 'bar'; | ||
// const bar = 'foo'; | ||
// export { foo, bar as bar2 }; | ||
const { specifiers } = path.node; | ||
if (specifiers && specifiers.length > 0) { | ||
specifiers.forEach(({ local, exported }) => { | ||
state.topLevelExports.set(exported.name, local); | ||
}); | ||
} | ||
// Remove the export | ||
path.remove(); | ||
// export class Foo {}; | ||
const identifier = declaration.get('id').node; | ||
state.topLevelExports.set(identifier.name, identifier); | ||
} | ||
}, | ||
ExportDefaultDeclaration(path) { | ||
const declaration = path.get('declaration'); | ||
if (declaration.type === 'Identifier' || declaration.type === 'MemberExpression') { | ||
// If export is a simple identifier we can use the node directly | ||
// | ||
// Identifier: | ||
// const foo = 'bar'; | ||
// export default foo; | ||
// | ||
// MemberExpression: | ||
// const obj = { | ||
// foo: 'bar' | ||
// }; | ||
// export default obj.foo; | ||
state.topLevelExports.set('default', declaration.node); | ||
// Remove the default export | ||
path.remove(); | ||
// Replace the export declaration with the actual declaration | ||
state.pathsToReplace.set(path, declaration); | ||
} | ||
else { | ||
// Export specifier | ||
// e.g: | ||
// const foo = 'bar'; | ||
// const bar = 'foo'; | ||
// export { foo, bar as bar2 }; | ||
const { specifiers } = path.node; | ||
if (specifiers && specifiers.length > 0) { | ||
specifiers.forEach(({ local, exported }) => { | ||
state.topLevelExports.set(exported.name, local); | ||
}); | ||
} | ||
else { | ||
// If we have a declaration in the default export we have to get the | ||
// identifier through the `id` property | ||
// e.g: | ||
// export default class Foo {} | ||
state.topLevelExports.set('default', declaration.get('id').node); | ||
// Replace the export declaration with the actual declaration | ||
path.replaceWith(declaration); | ||
} | ||
}, | ||
// Remove the export | ||
state.pathsToRemove.add(path); | ||
} | ||
}, | ||
ExportDefaultDeclaration(path) { | ||
const declaration = path.get('declaration'); | ||
if (declaration.type === 'Identifier' || declaration.type === 'MemberExpression') { | ||
// If export is a simple identifier we can use the node directly | ||
// | ||
// Identifier: | ||
// const foo = 'bar'; | ||
// export default foo; | ||
// | ||
// MemberExpression: | ||
// const obj = { | ||
// foo: 'bar' | ||
// }; | ||
// export default obj.foo; | ||
state.topLevelExports.set('default', declaration.node); | ||
// Remove the default export | ||
state.pathsToRemove.add(path); | ||
} | ||
else { | ||
// If we have a declaration in the default export we have to get the | ||
// identifier through the `id` property | ||
// e.g: | ||
// export default class Foo {} | ||
state.topLevelExports.set('default', declaration.get('id').node); | ||
// Replace the export declaration with the actual declaration | ||
state.pathsToReplace.set(path, declaration); | ||
} | ||
}, | ||
}; | ||
// When angular is used as global variable check for usage of the identifier | ||
if (angularGlobal) { | ||
visitor['Identifier'] = function (path) { | ||
const { node } = path; | ||
if (node.name === angularGlobal) { | ||
const parentProgram = path.findParent(path => path.isProgram()); | ||
registerAngularUse.set(parentProgram, true); | ||
} | ||
}; | ||
} | ||
return { | ||
name: 'ng-hot-reload', | ||
post() { | ||
// Clear the storage after each file | ||
state.topLevelImports.clear(); | ||
state.topLevelExports.clear(); | ||
state.pathsToRemove.clear(); | ||
state.pathsToReplace.clear(); | ||
}, | ||
visitor, | ||
}; | ||
} | ||
exports.default = default_1; |
{ | ||
"name": "babel-plugin-ng-hot-reload", | ||
"version": "2.1.0-alpha003", | ||
"version": "2.1.0-alpha004", | ||
"main": "dist/index.js", | ||
@@ -5,0 +5,0 @@ "author": { |
@@ -12,3 +12,3 @@ # 🔥 babel-plugin-ng-hot-reload | ||
> - [Webpack / TypeScript demo on CodeSandbox](https://codesandbox.io/s/github/ofhouse/babel-plugin-ng-hot-reload/tree/master/examples/typescript-webpack) | ||
> - [Parcel / TypeScript demo on CodeSandbox](https://codesandbox.io/s/github/ofhouse/babel-plugin-ng-hot-reload/tree/master/examples/typescript-parcel) (There are some issues, see: [FAQ](#known-issues-with-parcel)) | ||
> - [Parcel / TypeScript demo on CodeSandbox](https://codesandbox.io/s/github/ofhouse/babel-plugin-ng-hot-reload/tree/master/examples/typescript-parcel) (There is an issue with HTML import, see: [FAQ](#known-issues-with-parcel)) | ||
@@ -37,2 +37,3 @@ ```sh | ||
preserveState: true, | ||
angularReference: "require('angular'), angular", | ||
}, | ||
@@ -46,7 +47,8 @@ ], | ||
| Option | Default | Description | | ||
| --------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| `angularGlobal` | `false` (false or string) | Define whether angular is provided as global variable. Set to `'angular'` when `angular` is your global variable. | | ||
| `forceRefresh` | `true` (boolean) | Whether to reload window automatically when a change in source files can't be hot-reloaded. Note that Webpack DevServer also has its own option hotOnly, which should also be configured correctly to get the behaviour you want when hot reloading fails.<br />([ng-hot-reload option](https://github.com/noppa/ng-hot-reload#client-options)) | | ||
| `preserveState` | `true` (boolean) | If true, the library attempts to preserve some state in scope and controller instances when they are reloaded. Preserving state is an experimental feature and quite "hackish" so it may cause problems in some cases. Setting this to `false` might help if you run into weird errors.<br />([ng-hot-reload option](https://github.com/noppa/ng-hot-reload#client-options)) | | ||
| Option | Default | Description | | ||
| ------------------ | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| `angularGlobal` | `false` (false or string) | Define whether angular is provided as global variable. Set to `'angular'` when `angular` is your global variable. | | ||
| `forceRefresh` | `true` (boolean) | Whether to reload window automatically when a change in source files can't be hot-reloaded. Note that Webpack DevServer also has its own option hotOnly, which should also be configured correctly to get the behaviour you want when hot reloading fails.<br />([ng-hot-reload option](https://github.com/noppa/ng-hot-reload#client-options)) | | ||
| `preserveState` | `true` (boolean) | If true, the library attempts to preserve some state in scope and controller instances when they are reloaded. Preserving state is an experimental feature and quite "hackish" so it may cause problems in some cases. Setting this to `false` might help if you run into weird errors.<br />([ng-hot-reload option](https://github.com/noppa/ng-hot-reload#client-options)) | | ||
| `angularReference` | `"require('angular'), angular"` (string) | JavaScript expression that will be evaluated to get a reference to angular.<br />([ng-hot-reload option](https://github.com/noppa/ng-hot-reload#client-options)) | | ||
@@ -76,2 +78,37 @@ ## FAQ | ||
### Use AngularJS as global variable | ||
Per default the plugin looks for imports of `'angular'`-package and only adds the hot-module-reload code to this modules. | ||
However in some environments angular is used as a global variable without beeing imported, so the plugin has a `angularGlobal` setting which supports the use of angular as a global variable: | ||
```js | ||
// Default mode: Add hot-module-reload only to files which import 'angular' | ||
// app.module.js | ||
import * as angular from 'angular'; // or | ||
import angular from 'angular'; // or | ||
import 'angular'; | ||
angular.module('hot-reload-demo', []); | ||
//////////////////////////////////////////////////////////////////////////////// | ||
// Setting angularGlobal option: Use `angular` as global variable | ||
// .babelrc.json | ||
module.exports = { | ||
plugins: [ | ||
[ | ||
'babel-plugin-ng-hot-reload', | ||
{ | ||
angularGlobal: 'angular', // Name of the global angular variable | ||
}, | ||
], | ||
], | ||
}; | ||
// app.module.js | ||
angular.module('hot-reload-demo', []); | ||
``` | ||
For an example check out the [Webpack / Javascript example](./examples/javascript-webpack/). | ||
### Use together with `ngAnnotate` | ||
@@ -94,6 +131,5 @@ | ||
Unfortunatly there are currently some issues related to parcel: | ||
Unfortunatly there are currently an issue related to parcel: | ||
- No hot-module-replacement for HTML templates ([parcel#943](https://github.com/parcel-bundler/parcel/issues/943)) | ||
- Importing CSS from TypeScript file does not work (See [`fade.component.ts`](./examples/typescript-parcel/src/fade/fade.component.ts)) | ||
@@ -100,0 +136,0 @@ ## Author |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
19231
240
143