Skip to content

Commit

Permalink
Overhaul code structure and build process and add support for Firefox
Browse files Browse the repository at this point in the history
  • Loading branch information
itsojon committed Jun 7, 2020
1 parent e679018 commit 6752b4b
Show file tree
Hide file tree
Showing 28 changed files with 3,612 additions and 165 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.idea/
dist_*/
dists/
node_modules/
package-lock.json
yarn-error.log
*.zip
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
12.18.0
111 changes: 93 additions & 18 deletions Gruntfile.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,130 @@
const webpackConfigFn = require('./webpack.config.js');

module.exports = function(grunt) {
var EDITIONS = ['lite', 'beta', 'staging', 'power_user'];
const EDITIONS = ['beta', 'staging', 'production'];
const BROWSERS = ['chrome', 'firefox'];

var edition = grunt.option('edition');
if (EDITIONS.indexOf(edition) === -1) {
throw 'No extension specified. Should be one of: ' + EDITIONS;
const edition = grunt.option('edition');
const browser = grunt.option('browser');
const instanceUrl = grunt.option('instance');
try {
if (!EDITIONS.includes(edition)) {
throw 'No edition specified. Should be one of: ' + EDITIONS;
}
if (!BROWSERS.includes(browser)) {
throw 'No browser specified. Should be one of: ' + BROWSERS;
}
if (!instanceUrl) {
throw 'No instance provided, such as "https://trot.to"';
}
} catch (e) {
grunt.log.error(e);

grunt.fail.warn('Encountered error. Example usage:' +
' yarn dev --edition=production --browser=chrome --instance=https://trot.to');
}

var outputDir = 'dist_' + edition + '/';
const outputDir = `dists/dist_${edition}_${browser}/`;
const zipPath = outputDir.slice(0, outputDir.length - 1) + '.zip';

const webpackConfig = webpackConfigFn('', {
distDir: outputDir,
edition,
browser,
instanceUrl
});

const webpackOptions = {
stats: !process.env.NODE_ENV || process.env.NODE_ENV === 'development',
mode: process.env.NODE_ENV || 'development'
};

if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
// ensure dev webpack output doesn't use eval as that won't allow the dev extension to be loaded without messing
// with manifest.json
webpackOptions.devtool = 'source-map';
} else {
// Don't minimize when building to production in order to simplify review by browser teams. Performance impact
// should be immaterial.
webpackOptions.optimization = { minimize: false };
}

var config = {
pkg: grunt.file.readJSON('package.json'),

clean: {
output_dir: {
src: [
outputDir + '**',
zipPath
]
}
},
copy: {
main: {
files: [
{expand: true, cwd: 'src', src: '**', dest: outputDir},
{expand: true, cwd: 'editions/' + edition, src: 'icon.png', dest: outputDir}
{expand: true, cwd: 'src', src: '**/*.{html,png,json}', dest: outputDir},
{expand: true, cwd: 'editions/' + edition, src: 'icon*.png', dest: outputDir}
]
}
},
update_json: {
manifest_file: {
src: 'editions/' + edition + '/manifest_overrides.json',
src: [
`editions/${edition}/manifest_overrides.json`,
`editions/${edition}/${browser}/manifest_overrides.json`
],
dest: outputDir + 'manifest.json',
fields: ['name', 'version', 'description', 'permissions', 'optional_permissions', 'storage']
}
},
zip: {},
webpack: {
options: webpackOptions,
prod: webpackConfig,
dev: Object.assign({ watch: true }, webpackConfig)
},
concurrent: {
dev: {
tasks: ['watch:other_assets', 'webpack:dev'],
options: {
logConcurrentOutput: true
}
}
},
watch: {
scripts: {
files: ['src/**'],
tasks: ['build-dist'],
other_assets: {
files: [
'src/*.{html,png,json}',
'editions/**'
],
tasks: ['build_other'],
options: {
spawn: false
}
}
},
zip: {
'extension': {
cwd: outputDir,
src: outputDir + '*',
dest: zipPath
}
}
};

config.zip[outputDir.replace('/', '.zip')] = outputDir + '*';

grunt.initConfig(config);

grunt.loadNpmTasks('grunt-webpack');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-update-json');
grunt.loadNpmTasks('grunt-zip');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-concurrent');

grunt.registerTask('build-dist', function() {
grunt.task.run('copy', 'update_json', 'zip');
});
grunt.registerTask('build_other', ['copy', 'update_json']);

grunt.registerTask('build_dist', ['clean', 'build_other', 'webpack:prod', 'zip:extension']);

grunt.registerTask('default', ['build-dist', 'watch']);
grunt.registerTask('default', ['clean', 'build_other', 'concurrent:dev']);
};
116 changes: 101 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,119 @@
# Trotto Go Links Browser Extension

A browser extension providing go links goodness.
This repository contains all the code used in
the [Trotto go links Chrome extension](https://chrome.google.com/webstore/detail/trotto-go-links/nkeoojidblilnkcbbmfhaeebndapehjk)
and the [Trotto go links Firefox extension](https://addons.mozilla.org/en-US/firefox/addon/trotto-go-links).

## Building the extension and watching for changes
The build system for this extension is designed to make it easy to build the extension to work with
any go links instance (whether [Trotto](https://github.com/trotto/go-links) or any other go links
implementation) and deploy it via [chrome.google.com](https://chrome.google.com) and/or
[addons.mozilla.org](https://addons.mozilla.org).

A "go links instance" is any application that implements the concept
of [go links](https://www.trot.to/go-links), allowing people to create go links like `go/roadmap` or
`go/allhands` and share them with others in their organization. If a go links instance is hosted at, say,
`go.example.com`, `go/roadmap` would be resolved through `go.example.com/roadmap`. This extension does the
request rewriting needed to make `go/roadmap` redirect to `go.example.com/roadmap` and ultimately to the
destination for that go link.

Currently the extension supports **Chrome** and **Firefox**. If you're interested in support for other browsers,
please let us know by [submitting an issue](https://github.com/trotto/browser-extension/issues/new).

## Architecture

The browser-specific code is confined to API definitions
in [`src/apis`](https://github.com/trotto/browser-extension/blob/master/src/apis). The extension
background page and popup script have separate entry points for each browser, and each entry point
uses dependency injection to provide the browser-specific API implementation to the browser-agnostic
code. (h/t to Sergey Yavnyi at Grammarly
for [his great talk](https://www.youtube.com/watch?v=D2XFeihxaCU) on how Grammarly does
cross-browser extension development.)

The `yarn build` and `yarn dev` commands described below use webpack to bundle only the JavaScript
needed for the specified browser and use Grunt to prepare the other assets that are part of the extension,
including merging the core `manifest.json` file with edition-specific and browser-specific overrides.
The webpack configuration for these commands uses `webpack.DefinePlugin` to set the go links instance
base URL in the code according to the provided `instance` argument.

## Building the extension

In order to develop or build the extension, you'll need to have
[nvm](https://github.com/nvm-sh/nvm#installing-and-updating) and
[yarn](https://classic.yarnpkg.com/en/docs/install) installed.

### Extension "editions"

The `yarn dev` and `yarn build` commands support three "editions" of the extension, `beta`, `staging`,
and `production`. These editions can be used to roll out changes to smaller groups of users before
deploying them to your main user base. Each edition has a distinct icon that makes it easier for a
user to keep track of which edition they're using.

### Build arguments

Both the `yarn build` and `yarn dev` commands described below require three arguments:

1. `edition`, which is one of the editions described above
2. `browser`, which is the browser you want to build for or develop against (`chrome` or `firefox`)
3. `instance`, which is the full base URL for a go links instance (ex: `https://trot.to`)

### Building for deployment

The `yarn build` command will build the extension to the `dists/dist_{edition}_{browser}/` directory and
zip it to `dists/dist_{edition}_{browser}.zip`.

From the root directory:

*Chrome:*

```
npm install
grunt --edition=lite
nvm use
yarn install
yarn build --edition=production --browser=chrome --instance=https://trot.to
```

Once the Grunt task is running, you can load the unpacked extension from `dist_lite` as
described [here](https://developer.chrome.com/extensions/getstarted#manifest) and use the `dist_lite.zip` file
to [publish](https://developer.chrome.com/extensions/hosting) your own version of the extension.
*Firefox:*

TODO: Document how to make use of different editions.
```
nvm use
yarn install
yarn build --edition=production --browser=firefox --instance=https://trot.to
```

## Specifying a go links application instance
### Building for development

To use a go links application instance other than the managed instance of Trotto at https://trot.to, update
the `DEFAULT_INSTANCE` constant in `src/background.js`. Feel free to test with the test instance of Trotto at
https://latest-master.trotto.dev:
The `yarn dev` command will build the extension to the `dists/dist_{edition}_{browser}/` directory and
watch for changes, recompiling when changes are detected. You can
then [load the unpacked extension](https://developer.chrome.com/extensions/getstarted#manifest) (Chrome)
or [temporarily install the extension](https://extensionworkshop.com/documentation/develop/temporary-installation-in-firefox/)
(Firefox) to test it.

From the root directory:

*Chrome:*

```
const DEFAULT_INSTANCE = 'https://latest-master.trotto.dev';
nvm use
yarn install
yarn dev --edition=production --browser=chrome --instance=https://trot.to
```

## Bumping the extension version
*Firefox:*

To bump the extension version, update the `version` key in `editions/lite/manifest_overrides.json`.
```
nvm use
yarn install
yarn dev --edition=production --browser=firefox --instance=https://trot.to
```

## Specifying a go links instance

As described [above](#build-arguments), you can use the `instance` argument to build the extension for
any go links instance. For example, to build a Firefox extension for a go links instance
at https://latest-master.trotto.dev:

```
yarn build --edition=production --browser=chrome --instance=https://latest-master.trotto.dev
```

## FAQs

Expand Down
12 changes: 0 additions & 12 deletions editions/power_user/manifest_overrides.json

This file was deleted.

3 changes: 3 additions & 0 deletions editions/production/firefox/manifest_overrides.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"description": "This extension makes Trotto \"go links\" work seamlessly in Firefox."
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "Trotto Go Links",
"version": "1.21",
"name": "Trotto go links",
"version": "1.23",
"description": "This extension makes Trotto \"go links\" work seamlessly in Chrome."
}
2 changes: 1 addition & 1 deletion editions/staging/manifest_overrides.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "Trotto Go Links [STAGING]",
"version": "1.1",
"version": "1.3",
"description": "Staging version of the Trotto Go Links extension."
}
19 changes: 15 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{
"name": "chrome-extension",
"name": "trotto-go-links-extension",
"version": "1.0.0",
"description": "",
"license": "Apache-2.0",
"description": "A browser extension for go links",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "grunt",
"build": "export NODE_ENV=production && grunt build_dist"
},
"repository": {
"type": "git",
Expand All @@ -16,9 +19,17 @@
"homepage": "https://github.com/trotto/browser-extension#readme",
"devDependencies": {
"grunt": "^1.1.0",
"grunt-concurrent": "^3.0.0",
"grunt-contrib-clean": "^2.0.0",
"grunt-contrib-copy": "^1.0.0",
"grunt-contrib-watch": "^1.0.0",
"grunt-update-json": "^0.2.2",
"grunt-zip": "^0.17.1"
"grunt-webpack": "^3.1.3",
"grunt-zip": "^0.17.1",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11"
},
"dependencies": {
"webextension-polyfill": "^0.6.0"
}
}
36 changes: 36 additions & 0 deletions src/apis/chrome.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const browser = require('webextension-polyfill');


export class Api {
constructor() {
this.runtime = {
onInstalled: {
// note: this doesn't work: `addListener: chrome.runtime.onInstalled.addListener`
addListener: (callback) => chrome.runtime.onInstalled.addListener(callback)
}
};

this.storage = {
onChanged: {
addListener: (callback) => chrome.storage.onChanged.addListener(callback)
},
managed: {
get: browser.storage.managed.get
}
};

this.webRequest = {
onBeforeRequest: browser.webRequest.onBeforeRequest
};

this.tabs = {
query: browser.tabs.query,
create: browser.tabs.create,
remove: browser.tabs.remove,
update: browser.tabs.update,
onUpdated: {
addListener: (callback) => browser.tabs.onUpdated.addListener(callback)
}
};
}
}
Loading

0 comments on commit 6752b4b

Please sign in to comment.