I'm trying to build a Chrome extension with TypeScript.
The setup is quite simple:
In manifest.json
{
"permissions": [
"webRequest",
"webRequestBlocking",
"tabs",
"storage",
"http://*/",
"https://*/*"
],
"content_scripts": [
{
"matches": [ "http://*/*", "https://*/*" ],
"js": [ "scripts/require.js", "scripts/require-cs.js",
"scripts/main.js", "scripts/contentscript.js" ],
"run_at": "document_end",
"all_frames": true
}],
}
In model.ts:
export class WebPage
{
private id: number;
private processed: boolean;
get Id() { return this.id; }
set Id(value: number) { this.id = value };
get Processed() { return this.processed; }
set Processed(value: boolean) { this.processed = value };
constructor(id: number)
{
this.id = id;
this.processed = false;
}
}
When compiled the resulting JavaScript starts with:
define(["require", "exports"], function (require, exports) {
var WebPage = (function ()
{
//Code omitted to keep the SO question short
}});
In main.ts:
(function ()
{
console.log("Executing main.js");
requirejs.config(
{
baseUrl: "scripts", paths: { "model" : "model" }
});
})();
In contentscript.ts:
import model = require("model");
console.log("Processing page");
var page = new model.WebPage(1);
page.Processed = true;
console.log("Done processing page");
When compiled the resulting JavaScript looks like this:
define(["require", "exports", "model"], function (require, exports, model) {
console.log("Processing page");
var page = new model.WebPage(1);
page.Processed = true;
console.log("Done processing page");
});
And finally in require-cs.js:
console.log("Executing requirejs-cs.js");
require.load = function (context, moduleName, url) {
console.log("require.load called");
var xhr = new XMLHttpRequest();
xhr.open("GET", chrome.extension.getURL(url) + '?r=' + (new Date()).getTime(), true);
xhr.onreadystatechange = function (e) {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log("evaluating" + url)
eval(xhr.responseText);
context.completeLoad(moduleName);
}
};
xhr.send(null);
};
Which is what I found in all the other questions related to my issue.
All of this results in the following output when loading a page:
Uncaught Error: Mismatched anonymous define() module: function (require, exports, model) {
console.log("Processing page");
var page = new model.WebPage(1);
page.Processed = true;
console.log("Done processing page");
}
http://requirejs.org/docs/errors.html#mismatch
I've read those docs, I went through a lot of similar questions on SO,
but I haven't found anything that works for me yet.
Most questions deal with JavaScript specifically,
perhaps there is something missing on the TypeScript side of things?
Note: The TypeScript compiler is configured to use AMD.
The docs for the error message state:
Be sure to load all scripts that call define() via the RequireJS API. Do not manually code script tags in HTML to load scripts that have define() calls in them.
As described in this question, it seems that if you use import in a type script, it will be turned into a module when compiled using AMD. So by including "scripts/contentscript.js" as a content script you are trying to load a module script without using the RequireJS API. You can try removing contentscript.js from the content_scripts entry in the manifest and adding the following to main.js:
requirejs(["contentscript"], function() {});
Related
Update
It seems like webpack is causing the issues.
If I replace the dist/background.js with:
console.log("background is running"); // Now visible ✅
const handler = (req, sender, sendResponse) => {
switch (req.type) {
case "message":
sendResponse({ data: "hi" });
break;
default:
break;
}
};
chrome.runtime.onMessage.addListener(handler);
Both the console log (in service worker) and response (in popup) are observed. Also, there are no errors.
Time to investigate further
Update #2
Upon further inspection, I noticed that the webpack output is wrapped in a function, but never called:
{
/***/ "./src/background.ts":
/*!***************************!*\
!*** ./src/background.ts ***!
\***************************/
/***/ (function () {
console.log("background is running");
const handler = (req, sender, sendResponse) => {
switch (req.type) {
case "message":
sendResponse({ data: "hi" });
break;
default:
break;
}
};
chrome.runtime.onMessage.addListener(handler);
/***/
}) // <==== adding () will make it an IIFE and everything works!
},
Question is how to automate this?
Update #3
Seems like the IIFE trick I mentioned above only works when there are no imports in background.js. As soon as I add any import, I get an error that the background script is not valid.
Adding module type property to background does not help:
// manifest.json
{
...
"background": {
"service_worker": "background.js",
"type": "module"
},
...
}
Update #4
Turns out this was caused by vendor splitting optimization in webpack:
// webpack.config.json
{
...
optimization: {
runtimeChunk: "single",
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
enforce: true,
chunks: "all",
},
},
},
},
...
}
Once I removed this, everything started working properly!
Would be nice to keep this around, but it is just an optimization after all, so if it breaks things, best to get rid of it.
How I figured this out? Well as I mentioned, everything worked a couple of commits ago. Back then I didn't have this optimization, so I tried removing it again, and everything started working again like magic.
Original Question
I had this working previously, so I am sure my setup is correct, but regardless of what I try, I now get
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
Also, I cannot find a solution online which indicates anything that differs from my setup.
Here is a MWE
dist folder structure:
dist/background.js
dist/index.html
dist/manifest.json
dist/popup.js
dist/runtime.js
dist/vendors.js
other misc files
// manifest.json
{
...
"background": {
"service_worker": "background.js"
},
...
}
// src/components/App.tsx
export default function App(): JSX.Element {
...
useEffect(() => {
chrome.runtime.sendMessage({ type: 'someMessage' }, ({ data }) => {
console.log(data);
});
}, []);
...
}
// src/background.ts
import { TSentResponse } from "./typings/background";
import { executeResponse } from "./utils/background";
console.log('sanity check'); // <=== does not fire 🤔
// also doesn't seem to be called 😥
const handleMessage = (req: { type: string }, sender: chrome.runtime.MessageSender, res: (response?: unknown) => void) => {
switch (req.type) {
case 'someMessage':
// an IIFE (worked fine before)
break;
default:
break;
}
return true; // due to asynchronous nature
};
chrome.runtime.onMessage.addListener(handleMessage);
My service worker is registered properly:
When I said above that I cannot see the logs for background, I mean when I check in the service worker dev tools (from above image), not the popup dev tool.
When I open the popup, I get the following errors:
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
Error handling response: TypeError: Cannot destructure property 'data' of 'undefined' as it is undefined.
I also don't see the service worker actually being registered - it did register before...
Is this a bug with MV3?
My repository (not fully up to date, but can be used to quickly check the above)
Follow these steps:
To send a message from the popup.js to the background service worker, first you need to get current tab id. to get the current tab id do as below:
popup.js
const messageKey = 'key-message-from-popup-to-background';
// Listen to get current tab info from the content_popup
document.addEventListener('_listener_getCurrentTabInfo', function (e) {
const tab = e.detail.response;
// Send a message to the background,js
chrome.tabs.sendMessage(tab.id, messageKey);
});
// Send message to content_popup to get current tab info
document.dispatchEvent(new CustomEvent('_dispatch_getCurrentTabInfo'));
content_popup.js
const config = {
scripts: [
// add ".js" files to web_accessible_resources in manifest.json
"popup/popup.js"
]
};
// Listen to get current tab info from the background.js
document.addEventListener('_dispatch_getCurrentTabInfo', function (e) {
// Key to help
const type = 'get_current_tab_info';
// Send a message to the background.js to get current tab info
chrome.runtime.sendMessage({type: type}, response => {
document.dispatchEvent(new CustomEvent('_listener_getCurrentTabInfo', {detail: {'response': response}}));
});
});
// prepare and add scripts
var scriptList = config['scripts'];
for (var i = 0; i < scriptList.length; i++) {
var s = document.createElement('script');
s.src = chrome.runtime.getURL(scriptList[i]);
s.onload = function () {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
}
background.js
// Get messages
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
// If request related to fetch data from API
if (request.url && request.url !== '') {
// Fetch http request and send back the response
fetch(request.url, request.init).then(function (response) {
return response.text().then(function (text) {
sendResponse([{
body: text,
status: response.status,
statusText: response.statusText,
}, null]);
});
}, function (error) {
sendResponse([null, error]);
});
// If request do not related to fetch data from API
} else {
detectMessageType(request, sender, sendResponse);
}
return true;
});
function detectMessageType(request, sender, sendResponse) {
// Check background request type
if (request && request.type) {
switch (request.type) {
case 'get_current_tab_info': {
getCurrentTabInfo(tab => {
// Send current tab info back to content_popup
sendResponse(tab);
});
break;
}
}
}
});
function getCurrentTabInfo(callback) {
chrome.tabs.query({
active: true,
currentWindow: true
}, function (tab) {
callback(tab[0]);
});
}
Get message in contentScript
chrome.runtime.onMessage.addListener((message, sender, response) => {
const message = message;
// Write your codes
});
This is the project structure (partial)
Project
-- popup
---- popup.js
---- content_popup.js
---- popup.html
---- popup.css
-- content_extension.js
-- background.js
-- manifest.js
Add the following code to the end of the body tag inside the popup.html file
<script src="content_popup.js"></script>
Update the manifest.json file like below
"manifest_version": 3,
.
.
.
"content_scripts": [
{
"js": [
"content_extension.js"
]
}
],
"action": {
"default_icon": ...,
"default_title": ...,
"default_popup": "popup/popup.html"
},
"background": {
"service_worker": "background.js"
},
"web_accessible_resources": [
{
"resources": [
"popup/popup.html",
"popup/popup.js"
]
}
],
...
I am new to ReqireJs. I am trying to do the following:
I have a file(mymodule.js) with the following code:
require([
'jquery'
], function ($) {
var name;
$(document).ready(function() {
//do some load stuff
});
});
I am trying to include the file into some other file as follows:
require(['modules/mymodule.js']);
which works fine. My question is that how can I pass some parameters from require(['modules/mymodule.js']); into mymodules.js?
Thanks and regards.
I have found the solution here:
http://blog.novanet.no/4-strategies-for-passing-parameters-to-requirejs-modules/
I have used the code from 1st step from above link "Passing parameter to method" as follows:
define(
[
'jquery'
],
function($){
return {
sayHello: function(name){
alert("Hello " + name);
}
};
}
);
Then I have passed the parameter as follows:
require(['modules/mymodule.js'], function(mymodule){
mymodule.sayHello("World");
});
I'm trying to implement a few e2e tests in my aurelia-cli app. I've tried looking for docs or blogs but haven't found anything on e2e setup for the cli. I've made the following adjustments to the project.
first I added this to aurelia.json
"e2eTestRunner": {
"id": "protractor",
"displayName": "Protractor",
"source": "test/e2e/src/**/*.ts",
"dist": "test/e2e/dist/",
"typingsSource": [
"typings/**/*.d.ts",
"custom_typings/**/*.d.ts"
]
},
Also added the e2e tasks on aurelia_project/tasks:
e2e.ts
import * as project from '../aurelia.json';
import * as gulp from 'gulp';
import * as del from 'del';
import * as typescript from 'gulp-typescript';
import * as tsConfig from '../../tsconfig.json';
import {CLIOptions} from 'aurelia-cli';
import { webdriver_update, protractor } from 'gulp-protractor';
function clean() {
return del(project.e2eTestRunner.dist + '*');
}
function build() {
var typescriptCompiler = typescriptCompiler || null;
if ( !typescriptCompiler ) {
delete tsConfig.compilerOptions.lib;
typescriptCompiler = typescript.createProject(Object.assign({}, tsConfig.compilerOptions, {
// Add any special overrides for the compiler here
module: 'commonjs'
}));
}
return gulp.src(project.e2eTestRunner.typingsSource.concat(project.e2eTestRunner.source))
.pipe(typescript(typescriptCompiler))
.pipe(gulp.dest(project.e2eTestRunner.dist));
}
// runs build-e2e task
// then runs end to end tasks
// using Protractor: http://angular.github.io/protractor/
function e2e() {
return gulp.src(project.e2eTestRunner.dist + '**/*.js')
.pipe(protractor({
configFile: 'protractor.conf.js',
args: ['--baseUrl', 'http://127.0.0.1:9000']
}))
.on('end', function() { process.exit(); })
.on('error', function(e) { throw e; });
}
export default gulp.series(
webdriver_update,
clean,
build,
e2e
);
and the e2e.json
{
"name": "e2e",
"description": "Runs all e2e tests and reports the results.",
"flags": []
}
I've added a protractor.conf file and aurelia.protractor to the root of my project
protractor.conf.js
exports.config = {
directConnect: true,
// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome'
},
//seleniumAddress: 'http://0.0.0.0:4444',
specs: ['test/e2e/dist/*.js'],
plugins: [{
path: 'aurelia.protractor.js'
}],
// Options to be passed to Jasmine-node.
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000
}
};
aurelia.protractor.js
/* Aurelia Protractor Plugin */
function addValueBindLocator() {
by.addLocator('valueBind', function (bindingModel, opt_parentElement) {
var using = opt_parentElement || document;
var matches = using.querySelectorAll('*[value\\.bind="' + bindingModel +'"]');
var result;
if (matches.length === 0) {
result = null;
} else if (matches.length === 1) {
result = matches[0];
} else {
result = matches;
}
return result;
});
}
function loadAndWaitForAureliaPage(pageUrl) {
browser.get(pageUrl);
return browser.executeAsyncScript(
'var cb = arguments[arguments.length - 1];' +
'document.addEventListener("aurelia-composed", function (e) {' +
' cb("Aurelia App composed")' +
'}, false);'
).then(function(result){
console.log(result);
return result;
});
}
function waitForRouterComplete() {
return browser.executeAsyncScript(
'var cb = arguments[arguments.length - 1];' +
'document.querySelector("[aurelia-app]")' +
'.aurelia.subscribeOnce("router:navigation:complete", function() {' +
' cb(true)' +
'});'
).then(function(result){
return result;
});
}
/* Plugin hooks */
exports.setup = function(config) {
// Ignore the default Angular synchronization helpers
browser.ignoreSynchronization = true;
// add the aurelia specific valueBind locator
addValueBindLocator();
// attach a new way to browser.get a page and wait for Aurelia to complete loading
browser.loadAndWaitForAureliaPage = loadAndWaitForAureliaPage;
// wait for router navigations to complete
browser.waitForRouterComplete = waitForRouterComplete;
};
exports.teardown = function(config) {};
exports.postResults = function(config) {};
and I added a sample test in my test/e2e/src folder it doesn't get executed. I've also tried implementing a e2e test within the unit test folder since when I run au test I see that a chrome browser opens up.
describe('aurelia homepage', function() {
it('should load page', function() {
browser.get('http://www.aurelia.io');
expect(browser.getTitle()).toEqual('Home | Aurelia');
});
});
But this throws the error browser is undefined. Am I missing something with e2e testing with the cli? I know aurelia-protractor comes pre-installed but I don't see any way to run it.
I know this is a very late answer, but perhaps for others looking for an answer, you could try to import from the aurelia-protractor plugin
import {browser} from 'aurelia-protractor-plugin/protractor';
I am loading a 3rd party script that simply creates an overlay on a site it has been loaded onto. It works fine but sites using require.js seem to have intermittent issues I'm assuming with async loading some js files. Is there any type of callback or way to create a module in the DOM as sort of a listener to see if require.js is done loading?
I tried this but not even close:
define(function() {
alert('test');
return {};
});
and
define('myModule',
function () {
var myModule = {
doStuff:function(){
console.log('Yay! Stuff');
}
};
return myModule;
});
console.log(myModule);
I ended up just creating a secondary require.config file and loading the module with require if require is detected, seems to work fine.
if(typeof require === 'function') {
var base = 'http://' + someDomainVar;
function getJSTreeURL() {
var url = base + '/js/libs/jstree.min';
return url;
}
function getModuleURL() {
var url = base + '/module';
return url;
}
var reqTwo = require.config({
context: "instance2",
baseUrl: "instance2",
paths: {
'jq': 'http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min',
'jqTree': getJSTreeURL(),
'module': getModuleURL()
},
shim: {
'jq': {
exports: 'jq'
},
'jqTree': {
deps: ['jq'],
exports: 'jqTree'
},
'module': {
deps: ['jq', 'jqTree'],
exports: 'module'
}
}
});
reqTwo(['require', 'jq', 'jqTree'],
function(require, jq, jqTree) {
setTimeout(function() {
require(['module'],
function(module) {
console.log('loaded');
}
);
}, 0);
});
I'm trying to set up assetmanager
for my blog that has three modules
default
login
admin
I tried like
assets.json
{
"css": {
"app":{
"public/src/dist/default/css/dist.min.css": [
"public/src/assets/default/css/*.css"
]
},
"login":{
"public/src/dist/login/css/dist.min.css": [
"public/src/assets/default/css/*.css"
]
},
"admin":{
"public/src/dist/admin/css/dist.min.css": [
"public/src/assets/admin/css/*.css"
]
}
}
}
express.js
assetmanager.init({
js: assets.js,
css: assets.css,
debug: (process.env.NODE_ENV !== 'production'),
webroot: 'public'
});
// Add assets to local variables
app.use(function(req, res, next) {
res.locals({
assets: assetmanager.assets
});
next();
});
console.log(assetmanager.assets);
but console.log(assetmanager.assets);
give me a empty array []
so is there a way to manage assetmanager
with more than one module ?
the best way I found up to now
is like in my controllers:
'use strict';
var assetmanager = require('assetmanager');
exports.render = function(config) {
var assets = require(config.sroot+'/config/assets.json');
assetmanager.init({
js: assets.js.app,
css: assets.css.app,
debug: (process.env.NODE_ENV !== 'production'),
webroot: 'public'
});
return function(req, res) {
res.render('layouts/default', {appTitle:'ilwebdifabio',assets:assetmanager.assets});
}
};
but it's quite ugly and I have
duplicate code :(
END UP
There is no way to use assetmanager module
in different modules (login,default,admin).
Modules are automatically cached by the Node.js application upon first load. As such, repeated calls to require() - the global method that loads modules - will all result in a reference to the same cached object.
so you end up ie if you use in a module
to the have the dedicate assets in all other module so
I worked it out with :
'use strict';
var _ = require('lodash');
module.exports = function (path,route) {
var env = (process.env.NODE_ENV === 'production') ? 'production' : null;
var debug = (env !== 'production');
var data = require(path+'/config/assets.json');
var assets = {
css: [],
js: []
};
var getAssets = function (pattern) {
var files = [];
if (_.isArray(pattern)) {
_.each(pattern, function (path) {
files = files.concat(getAssets(path));
});
} else if (_.isString(pattern)) {
var regex = new RegExp('^(//)');
if (regex.test(pattern)) {
// Source is external
//For the / in the template against 404
files.push(pattern.substring(1));
} else {
files.push(pattern);
}
}
return files;
};
var getFiles = function () {
var current = data[route];
_.each(['css', 'js'], function (fileType) {
_.each(current[fileType], function (value, key) {
if (!debug) {
assets[fileType].push(key);
} else {
assets[fileType] = assets[fileType].concat(getAssets(value));
}
});
});
};
var getCurrentAssets = function(){
return assets;
};
getFiles();
return {
getCurrentAssets: getCurrentAssets
};
};
in the controller
var assetmanager = require(config.sroot+'/utils/assetsmanager')(config.sroot,'app');
res.render('layouts/default', {
assets:assetmanager.getCurrentAssets()
});
There is a new version of assetmanager 1.0.0 that I believe accomplishes what you're trying to do more effectively. In the new version you can break apart your assets into groups so that you can support multiple layouts. The github has a complete example here but essentially your asset files ends up looking something like this:
{
"main": {
"css": {
"public/build/css/main.min.css": [
"public/lib/bootstrap/dist/css/bootstrap.css",
"public/css/**/*.css"
]
},
"js": {
"public/build/js/main.min.js": [
"public/lib/angular/angular.js",
"public/js/**/*.js"
]
}
},
"secondary": {
"css": {
"public/build/css/secondary.min.css": [
"public/css/**/*.css"
]
},
"js": {
"public/build/js/secondary.min.js": [
"public/js/**/*.js"
]
}
}
}
And then in your layouts you just include the group you want. Hopefully that helps out.