Introducing 

Prezi AI.

Your new presentation assistant.

Refine, enhance, and tailor your content, source relevant images, and edit visuals quicker than ever before.

Loading…
Transcript

RequireJS In A Chrome Extension?!

The How And Why

What is RequireJS?

  • JavaScript file and module loader
  • uses AMD (Asynchronous Module Definition)
  • asynchronous module loading is well suited to browser environments

CommonJS module:

var dep1 = require('dependency1');

var dep2 = require('dependency2');

/* do something here with deps...*/

exports.result = myResult;

AMD vs CommonJS

RequireJS can be used in a Chrome Extension and works well, although the setup can be a bit finicky...

Conclusion

AMD dependencies are specified up-front in array. This allows the AMD loader to more easily optimize loading and execution of modules in the browser environment.

RequireJS module:

define(['dependency1', 'dependency2'],

function(dep1, dep2) {

/* do something here with deps...*/

return { result: myResult };

});

test/modules/requireConfig.js:

// RequireJS configuration module

// Takes configuration from project and modifies it for use with unit tests

;(function() {

var fs = require('fs');

exports.init = function() {

// synchronously read script file containing requirejsConfig

try {

var data = fs.readFileSync('js/requireConfig.js', 'ascii');

}

catch(e) {

console.error(e);

return {};

}

// evaluate script to initialise requirejsConfig object

eval(data.toString());

// absolute path prefix for test suite

var pathPrefix = __dirname + '/../..';

// now we modify the config

var rc = requirejsConfig;

// modify the baseUrl to point to our modules

rc.baseUrl = pathPrefix + rc.baseUrl;

// modify any path urls

var path;

for (path in rc.paths) {

if (typeof rc.paths[path] === 'string') {

rc.paths[path] = pathPrefix + rc.paths[path];

}

}

// optionally do other clever things to the config..

return rc;

};

}).call(this);

Why in Chrome Extensions?

  • it works (by injecting script tags into the html document)
  • due to manifest v 2.0 limitations (banning eval in the background script) there aren't many other options right now

test/specs/myModule.spec.js:

// initialize RequireJS

var requirejs = require('requirejs');

var rc = require('../modules/requireConfig');

requirejs.config(rc.init());

// run unit test with dependencies

var myModule = requirejs('typeA/myModule');

describe("Sample unit test", function() {

it ("myModule.color should equal 'red' and baseColor should equal 'blue'",

function() {

expect(myModule.color).toEqual('red');

expect(myModule.myBaseColor).toEqual('blue');

});

});

Unit Tests

Any questions?

run-tests.sh:

#!/bin/bash

# if node modules are not installed, install them..

if [ ! -d "node_modules" ]

then

echo "Installing dependencies.."

npm install

fi

# run tests

echo "Starting test execution:"

echo ""

jasmine-node --verbose test/specs

package.json:

{

"name": "Test",

"version": "0.0.1",

"devDependencies": {

"requirejs" : ">= 2.0.4"

}

}

The Goal

  • we want the same module mechanism to be used extension-wide
  • we want a single module definition to be usable anywhere in the extension (provided it doesn't access the background-only chrome API)
  • we want to be able to unit-test the modules
  • we want as little repeated code as possible

js/content.js:

requirejs.config(requirejsConfig);

requirejs(['jquery', 'typeA/myModule'],

function($, myModule) {

console.log('My favourite color: ' + myModule.color);

console.log('My second favourite color: ' + myModule.baseColor);

});

js/requireContent.js:

require.load = function (context, moduleName, url) {

var xhr;

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) {

eval(xhr.responseText);

context.completeLoad(moduleName)

}

};

xhr.send(null);

};

The Content Script

The Setup

manifest.json (relevant parts):

...

"content_scripts": [

{

"matches": ["<all_urls>"],

"js": [

"js/lib/require.js",

"js/requireContent.js",

"js/requireConfig.js",

"js/content.js"

]

}

],

...

"web_accessible_resources": [

"js/modules/*",

"js/lib/*"

]

...

Extension Folder

/html - contains any html files used by extension (background.html, popup.html)

/js - contains all extension JavaScript code. In the root are main js files: background.js, content.js, popup.js and RequireJS specific files.

/lib - external libraries

/modules - all extension-specific code in AMD modules

/typeA - One type of module.

/typeB - Another type of module.

/test - contains all test suite Javascript code for Jasmine-node.

/specs - unit test modules

/modules - unit test-specific utility and RequireJS configuration modules

js/background.js:

requirejs.config(requirejsConfig);

requirejs(['jquery', 'typeA/myModule'],

function($, myModule) {

console.log('My favourite color: ' + myModule.color);

console.log('My second favourite color: ' + myModule.baseColor);

});

js/modules/typeB/myBaseModule.js:

define({

color: 'blue'

});

html/background.html:

<!doctype html>

<html>

<head>

<script src="../js/lib/require.js"></script>

<script src="../js/requireConfig.js"></script>

<script src="../js/lib/jquery.js"></script>

<!-- script tags for named AMD or non-AMD libraries here -->

<script src="../js/background.js"></script>

</head>

<body>

</body>

</html>

js/modules/typeA/myModule.js:

define(['typeB/myBaseModule'],

function(myBaseModule) {

return {

color: 'red',

baseColor: myBaseModule.color

};

});

js/requireConfig.js:

var requirejsConfig = {

// by default load any module IDs from js/modules

baseUrl: '/js/modules',

// optionally specify different paths for specific modules

paths: {

lib: '/js/lib'

}

};

Some (Unuseful) Modules

manifest.json (relevant parts):

...

"background": {

"page": "html/background.html"

},

...

The Background Script

Learn more about creating dynamic, engaging presentations with Prezi