Describe the bug
I try to use angular to build chrome extension. To run, extension must have a background script.
I place a background.ts file in the src folder, and add custom build to angular.json serve command:
"serve": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {
"path": "./custom-webpack.config.js",
"replaceDuplicatePlugins": true,
"mergeStrategies": {
"externals": "prepend"
}
},
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": true,
"assets": ["src/favicon.ico", "src/assets", "src/manifest.json"],
"styles": ["src/styles.styl"],
"scripts": []
},
the custom-webpack.config.js file with the new background "entry" to enhance webpack build:
const ExtensionReloader = require('webpack-extension-reloader')
module.exports = {
watch: true,
entry: {
background: './src/background/index.ts'
},
output: {
filename: '[name].bundle.js'
},
plugins: [new ExtensionReloader({ reloadPage: true })]
}
the result seems to be good:
chunk {background} background.bundle.js, background.bundle.js.map (background) 41.2 kB [initial] [rendered]
chunk {main} main.bundle.js, main.bundle.js.map (main) 29.9 kB [initial] [rendered]
chunk {polyfills} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 582 kB [initial] [rendered]
chunk {runtime} runtime.bundle.js, runtime.bundle.js.map (runtime) 6.08 kB [entry] [rendered]
chunk {styles} styles.bundle.js, styles.bundle.js.map (styles) 16.8 kB [initial] [rendered]
chunk {vendor} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.92 MB [initial] [rendered]
but the compiled script is not executed when loaded by chrome extension (via manifest.json file)
{
...,
"background": {
"scripts": ["background.bundle.js"],
"persistent": true
},
"permissions": [
"tabs",
"activeTab",
"background",
"webNavigation",
"webRequest",
"storage"
]
}
To Reproduce
Steps to reproduce the behavior:
Expected behavior
Background to be executed
Screenshots
script is well loaded by extension:

But nothing exectuded:

Additional context
package.json
{
"name": "extension-ng8",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~8.0.0",
"@angular/common": "~8.0.0",
"@angular/compiler": "~8.0.0",
"@angular/core": "~8.0.0",
"@angular/forms": "~8.0.0",
"@angular/platform-browser": "~8.0.0",
"@angular/platform-browser-dynamic": "~8.0.0",
"@angular/router": "~8.0.0",
"@types/chrome": "^0.0.86",
"rxjs": "~6.4.0",
"tslib": "^1.9.0",
"webpack-extension-reloader": "^1.1.0",
"zone.js": "~0.9.1"
},
"devDependencies": {
"@angular-builders/custom-webpack": "^8.0.2",
"@angular-devkit/build-angular": "~0.800.0",
"@angular/cli": "~8.0.2",
"@angular/compiler-cli": "~8.0.0",
"@angular/language-service": "~8.0.0",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~8.9.4",
"codelyzer": "^5.0.0",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.1.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tslint": "~5.15.0",
"typescript": "~3.4.3",
"webpack-chrome-extension-reloader": "^1.3.0"
}
}
I'm not sure if I can help you with that. It seems that the builder performs OK. The script is generated, it is attached to the index.html, it is fetched by your Chrome extension (and therefore should be executed).
What exactly is not working? The extension is working? The background script? The main script? What actually defines the main script as an entry point of your Chrome extension?
Hi! thanks @meltedspark
The background script is not working
In fact, it's compiled but as an "exported" script, but therefore it is not executed (like if it has to be imported by another "main" script):
/***/ "./src/background/index.ts":
/*!*********************************!*\
!*** ./src/background/index.ts ***!
\*********************************/
/*! no static exports found */
/***/ (function(module, exports) {
console.log('will i be executed?');
/***/ })
},[["./src/background/index.ts","runtime"]]]);
//# sourceMappingURL=background.bundle.js.map
The extension itself (angular build) is ok (index.html + main.bundle.js + polyfills + runtime + styles + vendor).
The background.bundle.js is compiled but not executed.
I don't know how to define another "main" for background script to run itself without compromising angular main
Can you point me out to the concrete place in Chrome Extensions documentation where the background script described? I don't quite understand what actually connects this script to the extension.
Yes with pleasure :)
https://developer.chrome.com/extensions/background_pages
OK, it seems pretty straight forward but I still don't have the full picture. I'm gonna need more information. Any chance that you can share a minimal reproduction repo?
Yes: https://github.com/Passiverecords/ChromeExtensionNg8
Just run yarn install && ng serve
a dist folder will be created and you can add it to chrome as an "unpacked" extension, and then inspect the background view (in the chrome extension manager)
Ok, it took me a while but I figured out what your problem is. If you take a look at the resulting bundle (background.bundle.js) you'll see this code at the end of the bundle:
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["background"],{
/***/ "./src/background/index.ts":
/*!*********************************!*\
!*** ./src/background/index.ts ***!
\*********************************/
/*! no static exports found */
/***/ (function(module, exports) {
console.log('will i be executed? Will I?');
/***/ })
},[["./src/background/index.ts","runtime"]]]);
This code adds your chunk as a webpack module but it doesn't actually run your module. Who does? runtime.bundle.js. This chunk is responsible for setting up webpack environment and installing JSONP callback for chunks loading. This code will eventually execute your chunk once it is loaded.
Thus, just add runtime.bundle.js to your manifest background.scripts array and you're good to go.
Boy I'm gonna write a blog post about this stuff.
Another insight - don't abuse ng serve. You use custom-webpack:browser builder for serve target, which is technically fine but very confusing. Instead, use build target for that and run it with ng build --watch. This way you keep your serve clean and also can build your extension with or w/o watching. Just don't forget to remove watch: true from your webpack config.
I've tested it, it works, the background page reloads when you change the file.
Best of luck!
@meltedspark thanks! it works!
manifest.json is now:
"background": {
"scripts": ["background.bundle.js", "runtime.bundle.js"],
"persistent": true
},
I was using serve has the concept of the "developing phase" but I understand that it could be confusing...
Thanks one more time!
@meltedspark, thinking about dealing between serve or build, the problem is that the custom webpack config include the ExtensionReloader plugin, which has to be ignored during real "build" phase... If I could link --watch option of the build to the ExtensionReloader, it would be great :)
Well, there are actually two options:
configurations.development configuration under build target and specify a different (the second one) webpack config in there. At last, run ng build --development to use this configuration.watch property in it. If it's there push ExtensionReloader into plugins array.adding runtime.js to the manifest.json works for getting background running. @just-jeb did you get the content-script to copy over without the webpackJsonp wrapper? Including runtime.js as part of the content_scripts array doesn't seem to work in the same manner.
Most helpful comment
Ok, it took me a while but I figured out what your problem is. If you take a look at the resulting bundle (
background.bundle.js) you'll see this code at the end of the bundle:This code adds your chunk as a webpack module but it doesn't actually run your module. Who does?
runtime.bundle.js. This chunk is responsible for setting up webpack environment and installing JSONP callback for chunks loading. This code will eventually execute your chunk once it is loaded.Thus, just add
runtime.bundle.jsto your manifestbackground.scriptsarray and you're good to go.Boy I'm gonna write a blog post about this stuff.
Another insight - don't abuse
ng serve. You usecustom-webpack:browserbuilder forservetarget, which is technically fine but very confusing. Instead, usebuildtarget for that and run it withng build --watch. This way you keep yourserveclean and also can build your extension with or w/o watching. Just don't forget to removewatch: truefrom your webpack config.I've tested it, it works, the background page reloads when you change the file.
Best of luck!