├── extension-app ├── static │ └── .gitkeep ├── .eslintignore ├── config │ ├── prod.env.js │ ├── dev.env.js │ └── index.js ├── build │ ├── logo.png │ ├── vue-loader.conf.js │ ├── check-versions.js │ ├── build.js │ ├── webpack.prod_com.conf.js │ ├── webpack.base.conf.js │ ├── utils.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── src │ ├── assets │ │ ├── icon.png │ │ ├── logo.png │ │ └── conan-hand.png │ ├── option │ │ ├── AppModel.js │ │ ├── main.js │ │ └── App.vue │ ├── app │ │ ├── common │ │ │ └── util.js │ │ ├── router │ │ │ └── index.js │ │ ├── main.js │ │ ├── AppModel.js │ │ ├── App.vue │ │ └── components │ │ │ └── HelloWorld.vue │ ├── config │ │ └── message.js │ ├── manifest.json │ ├── background │ │ └── bg.js │ └── contentScript │ │ ├── runtime.js │ │ └── cs.js ├── .editorconfig ├── .gitignore ├── .babelrc ├── .postcssrc.js ├── entry │ ├── app.html │ └── option.html ├── README.md ├── manifest.json ├── .eslintrc.js └── package.json ├── LICENSE └── README.md /extension-app/static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extension-app/.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /extension-app/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /extension-app/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeDebugTest/chrome-extension-vue-app/master/extension-app/build/logo.png -------------------------------------------------------------------------------- /extension-app/src/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeDebugTest/chrome-extension-vue-app/master/extension-app/src/assets/icon.png -------------------------------------------------------------------------------- /extension-app/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeDebugTest/chrome-extension-vue-app/master/extension-app/src/assets/logo.png -------------------------------------------------------------------------------- /extension-app/src/assets/conan-hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeDebugTest/chrome-extension-vue-app/master/extension-app/src/assets/conan-hand.png -------------------------------------------------------------------------------- /extension-app/src/option/AppModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * page data storage, api response data 3 | */ 4 | export default class AppModel { 5 | init() { 6 | // init 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /extension-app/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /extension-app/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /extension-app/src/option/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './APP'; 3 | 4 | /* eslint-disable no-new */ 5 | new Vue({ 6 | el: '#app', 7 | components: {App}, 8 | template: '' 9 | }); 10 | -------------------------------------------------------------------------------- /extension-app/src/app/common/util.js: -------------------------------------------------------------------------------- 1 | export function sendMessage(data, cb) { 2 | window.chrome.tabs.query({active: true, currentWindow: true}, tabs => { 3 | window.chrome.tabs.sendMessage(tabs[0].id, data, cb); 4 | }); 5 | }; 6 | -------------------------------------------------------------------------------- /extension-app/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | /crx/ 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Editor directories and files 10 | .idea 11 | .vscode 12 | *.suo 13 | *.ntvs* 14 | *.njsproj 15 | *.sln 16 | -------------------------------------------------------------------------------- /extension-app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /extension-app/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /extension-app/src/config/message.js: -------------------------------------------------------------------------------- 1 | export default { 2 | INIT: 1, 3 | 4 | GET_PERMISSION_RULES: 2, 5 | RECEIVE_PERMISSION: 3, 6 | 7 | SET_VALUE_ON_PAGE: 4, 8 | SET_VALUE: 5, 9 | 10 | RECEIVE_ERROR_ON_PAGE: 6, 11 | RECEIVE_ERROR: 7, 12 | 13 | NODATA_ON_PAGE: 8, 14 | NODATA: 9 15 | }; 16 | -------------------------------------------------------------------------------- /extension-app/src/app/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import HelloWorld from '../components/HelloWorld'; 4 | 5 | Vue.use(Router); 6 | 7 | export default new Router({ 8 | routes: [{ 9 | path: '/', 10 | name: 'HelloWorld', 11 | component: HelloWorld 12 | }] 13 | }); 14 | -------------------------------------------------------------------------------- /extension-app/entry/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | extension-app 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /extension-app/entry/option.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | extension-option 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /extension-app/src/app/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue'; 4 | import App from './App'; 5 | import router from './router'; 6 | 7 | Vue.config.productionTip = false; 8 | 9 | /* eslint-disable no-new */ 10 | new Vue({ 11 | el: '#app', 12 | router, 13 | components: { 14 | App 15 | }, 16 | template: '' 17 | }); 18 | -------------------------------------------------------------------------------- /extension-app/README.md: -------------------------------------------------------------------------------- 1 | # extension-app 2 | 3 | > chrome extension app use vue 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | ``` 20 | 21 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 22 | -------------------------------------------------------------------------------- /extension-app/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction ? 6 | config.build.productionSourceMap : 7 | config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /extension-app/src/app/AppModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * page data storage, api response data 3 | */ 4 | import MESSAGE from '../config/message'; 5 | import {sendMessage} from './common/util'; 6 | 7 | export default class AppModel { 8 | bindEvents() { 9 | window.chrome.runtime.onMessage.addListener((request) => { 10 | this.waitingMessage = false; 11 | switch (request.code) { 12 | case MESSAGE.NODATA: 13 | this.redirect('/not_found'); 14 | default: 15 | break; 16 | } 17 | }); 18 | } 19 | 20 | init() { 21 | sendMessage({code: MESSAGE.INIT}); 22 | this.bindEvents(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /extension-app/src/app/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | 34 | -------------------------------------------------------------------------------- /extension-app/src/option/App.vue: -------------------------------------------------------------------------------- 1 | 7 | < 25 | 37 | -------------------------------------------------------------------------------- /extension-app/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrome extension", 3 | "version": "0.0.1", 4 | "icons": { 5 | "128": "icons/xxxx.png" 6 | }, 7 | "permissions": [ 8 | "", 9 | "tabs", 10 | "activeTab", 11 | "clipboardRead", 12 | "clipboardWrite" 13 | ], 14 | "background": { 15 | "scripts": [ 16 | "background/bg.js" 17 | ], 18 | "persistent": false 19 | }, 20 | "options_page": "option/option.html", 21 | "content_scripts": [{ 22 | "matches": [ 23 | "" 24 | ], 25 | "js": [ 26 | "contentScript/cs.js" 27 | ] 28 | }], 29 | "page_action": { 30 | "default_icon": "icons/xxxx.png", 31 | "default_title": "chrome extension", 32 | "default_popup": "main/index.html" 33 | }, 34 | "web_accessible_resources": [ 35 | "contentScript/runtime.js" 36 | ], 37 | "manifest_version": 2, 38 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'" 39 | } -------------------------------------------------------------------------------- /extension-app/src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chrome Extension VUE APP", 3 | "version": "1.0.0", 4 | "icons": { 5 | "128": "icons/icon.png" 6 | }, 7 | "permissions": [ 8 | "", 9 | "tabs", 10 | "activeTab", 11 | "clipboardRead", 12 | "clipboardWrite" 13 | ], 14 | "background": { 15 | "scripts": [ 16 | "background/bg.js" 17 | ], 18 | "persistent": false 19 | }, 20 | "options_page": "option/option.html", 21 | "content_scripts": [{ 22 | "matches": [ 23 | "" 24 | ], 25 | "js": [ 26 | "contentScript/cs.js" 27 | ] 28 | }], 29 | "page_action": { 30 | "default_icon": "icons/conan-hand.png", 31 | "default_title": "extension test", 32 | "default_popup": "app/plugin.html" 33 | }, 34 | "web_accessible_resources": [ 35 | "contentScript/runtime.js" 36 | ], 37 | "manifest_version": 2, 38 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'" 39 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 liuye 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /extension-app/src/background/bg.js: -------------------------------------------------------------------------------- 1 | import MESSAGE from '../config/message'; 2 | class Main { 3 | bindEvents() { 4 | window.chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { 5 | switch (request.code) { 6 | case MESSAGE.GET_PERMISSION_RULES: 7 | // define by your site address 8 | if (!window.localStorage.getItem('extensionPermission')) { 9 | window.localStorage.setItem('extensionPermission', '/https?:\\/\\/.*\\.github\\.com/'); 10 | } 11 | sendResponse({ permission: window.localStorage.getItem('extensionPermission') }); 12 | break; 13 | case MESSAGE.RECEIVE_PERMISSION: // 授权 14 | window.chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { 15 | window.chrome.pageAction.show(tabs[0].id); 16 | }); 17 | break; 18 | default: 19 | break; 20 | } 21 | }, false); 22 | } 23 | 24 | async run() { 25 | this.bindEvents(); 26 | } 27 | } 28 | 29 | let main = new Main(); 30 | main.run(); 31 | -------------------------------------------------------------------------------- /extension-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 13 | extends: 'standard', 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'vue', 'html' 17 | ], 18 | // add your custom rules here 19 | 'rules': { 20 | // allow paren-less arrow functions 21 | 'arrow-parens': 0, 22 | // allow async-await 23 | 'generator-star-spacing': 0, 24 | // allow debugger during development 25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 26 | // fix unused var error for JSX custom tags 27 | 'vue/jsx-uses-vars': 2, 28 | 'no-throw-literal': 0, 29 | 30 | 'semi': ['error', 'always'], 31 | 'indent': ['error', 4, {SwitchCase: 1}], 32 | 'space-before-function-paren': ['error', { 33 | anonymous: 'always', 34 | named: 'never', 35 | asyncArrow: 'always' 36 | }], 37 | 'brace-style': ['error', 'stroustrup'], 38 | 'operator-linebreak': ['error', 'before'], 39 | 'eol-last': 'error', 40 | 'no-new': 0, 41 | 'no-else-return': 0, 42 | 'padded-blocks': 0, 43 | 'no-fallthrough': 'off', 44 | 'default-case': 'error' 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /extension-app/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec(cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [{ 12 | name: 'node', 13 | currentVersion: semver.clean(process.version), 14 | versionRequirement: packageConfig.engines.node 15 | }] 16 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | const warnings = [] 27 | 28 | for (let i = 0; i < versionRequirements.length; i++) { 29 | const mod = versionRequirements[i] 30 | 31 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 32 | warnings.push(mod.name + ': ' + 33 | chalk.red(mod.currentVersion) + ' should be ' + 34 | chalk.green(mod.versionRequirement) 35 | ) 36 | } 37 | } 38 | 39 | if (warnings.length) { 40 | console.log('') 41 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 42 | console.log() 43 | 44 | for (let i = 0; i < warnings.length; i++) { 45 | const warning = warnings[i] 46 | console.log(' ' + warning) 47 | } 48 | 49 | console.log() 50 | process.exit(1) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /extension-app/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | const webpackProdCommonConfig = require('./webpack.prod_com.conf') 14 | 15 | const buildTasks = config.build.entries.map(entry => webpackConfig(entry)); 16 | buildTasks.push(webpackProdCommonConfig); 17 | 18 | const spinner = ora('building for production...') 19 | spinner.start() 20 | 21 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 22 | if (err) throw err 23 | 24 | Promise.all(buildTasks.map(task => { 25 | return new Promise((resolve, reject) => { 26 | webpack(task, (err, stats) => { 27 | if (err) throw err 28 | process.stdout.write(stats.toString({ 29 | colors: true, 30 | modules: false, 31 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 32 | chunks: false, 33 | chunkModules: false 34 | }) + '\n\n') 35 | 36 | if (stats.hasErrors()) { 37 | console.log(chalk.red(' Build failed with errors.\n')) 38 | process.exit(1) 39 | reject() 40 | } 41 | resolve() 42 | }) 43 | }) 44 | })) 45 | .then(() => { 46 | spinner.stop() 47 | console.log(chalk.cyan(' Build complete.\n')) 48 | console.log(chalk.yellow( 49 | ' Tip: built files are meant to be served over an HTTP server.\n' + 50 | ' Opening index.html over file:// won\'t work.\n' 51 | )) 52 | }) 53 | .catch(err => { 54 | spinner.stop() 55 | console.log(chalk.cyan(' Build failed.\n')) 56 | }) 57 | 58 | }) 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chrome-extension-vue-app 2 | ## extension-vue-app 3 | > 1. 选项页面option与弹出页面app分开单独编译 4 | > 2. 编译后生成crx文件夹,在chrome开发者模式下-->加载已解压的扩展程序 5 | > 3. 编译:Webpack + vue-loader 可以在 CSP 环境中完美运行。 6 | 7 | 8 | ## chrome 插件开发指南: 9 | * manifest.json 10 | - chrome 插件配置文件,可以看作插件的‘入口’ 11 | * permissions 12 | - 允许插件做哪些事情,访问哪些站点. 13 | - 假如一个插件的"permissions"里写有“http://*.hacker.com/”,那么这个插件就被允许访hacker.com上的所有内容. 14 | * page action 15 | - 指定插件图标,标题,弹出页面的html 16 | * background 17 | - 可以认为是chrome插件的主程序。插件被启用时,chrome就给插件开辟了一个独立的javascript运行环境,用来跑指定的js脚本。 18 | * content script 19 | - 要注入到页面中的脚本,插件允许我们往网页中注入脚本. 20 | * [google开发文档](https://crxdoc-zh.appspot.com/extensions/devguide) 21 | 22 | ## Content Security Policy 23 | 24 | ### Google官方文档: 25 | > 1. [Content Security Policy (CSP)](https://developer.chrome.com/extensions/contentSecurityPolicy) 26 | > 2. [An Introduction to Content Security Policy](https://www.html5rocks.com/en/tutorials/security/content-security-policy/) 27 | > 3. [Content Security Policy Reference](https://content-security-policy.com/) 28 | > 4. [Content Security Policy Level 3](https://w3c.github.io/webappsec-csp/) 29 | 30 | 31 | ### [vue 官网:](https://cn.vuejs.org/v2/guide/installation.html#CSP-%E7%8E%AF%E5%A2%83) 32 | 有些环境,如 Google Chrome Apps,会强制应用内容安全策略 (CSP),不能使用 new Function() 对表达式求值。这时可以用 CSP 兼容版本 33 | 34 | 35 | ### CSP指北: 36 | > 1. manifest_version为2的扩展才会默认开启内容安全策略。 37 | > 2. 没有定义 manifest_version 的扩展安装包默认是没有内容安全策略的。 38 | > 3. Inline JavaScript和eval一样危险,将不会被执行,CSP规则将同时禁止内嵌 39 | > 4. 只有扩展包内的脚本和资源才会被加载!通过Web即时下载的将不会被加载! 这确保您的扩展只执行已经打包在扩展之中的可信代码,从而避免了线上的网络攻击者通过恶意重定向您所请求的Web资源所带来的安全隐患。 40 | > 5. ***放宽默认策略*** 通过添加 'unsafe-eval' 来实现,即在mainfest.json中加入下面代码: 41 | > ```javascript 42 | > { 43 | > ... 44 | > "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", 45 | > ... 46 | > } 47 | > ``` 48 | > 6. **怎样引入外部的JavaScript或者资源** 可以通过将HTTPS源的脚本加入白名单来放宽“只加载本地脚本和资源”策略。如: 49 | > ```javascript 50 | > { 51 | > ... 52 | > "content_security_policy": "script-src 'self' 'unsafe-eval' https://maps.googleapis.com/; object-src 'self'", 53 | > ... 54 | > } 55 | > ``` 56 | 57 | 58 | -------------------------------------------------------------------------------- /extension-app/src/contentScript/runtime.js: -------------------------------------------------------------------------------- 1 | class RunTime { 2 | constructor() { 3 | let messageInput = document.getElementById('extensionMessageJson'); 4 | this.MESSAGE = JSON.parse(messageInput.value); 5 | document.body.removeChild(messageInput); 6 | }x 7 | 8 | bindEvents() { 9 | window.addEventListener('message', e => { 10 | if (e.data && e.data.code) { 11 | switch (e.data.code) { 12 | case this.MESSAGE.INIT: 13 | // 初始化处理 bala bala 14 | let nodata = true; 15 | if (nodata) { 16 | window.postMessage({ 17 | code: this.MESSAGE.NODATA_ON_PAGE 18 | }, '*'); 19 | } 20 | 21 | break; 22 | case this.MESSAGE.SET_VALUE_ON_PAGE: 23 | let {name, value} = e.data; 24 | if (name != null && value) { 25 | // bala bala 处理操作 26 | 27 | try { 28 | // send success event 29 | } 30 | catch (err) { 31 | window.postMessage({ 32 | code: this.MESSAGE.RECEIVE_ERROR_ON_PAGE, 33 | message: err.message 34 | }, '*'); 35 | } 36 | 37 | // 还可以 send error message 38 | /* 39 | window.postMessage({ 40 | // code: this.MESSAGE.RECEIVE_ERROR_ON_PAGE, 41 | // message: '当前页面错误 :-(' 42 | // }, '*'); 43 | */ 44 | } 45 | break; 46 | default: 47 | break; 48 | } 49 | } 50 | }); 51 | } 52 | } 53 | 54 | let rt = new RunTime(); 55 | rt.bindEvents(); 56 | -------------------------------------------------------------------------------- /extension-app/src/app/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 96 | 97 | 98 | 115 | -------------------------------------------------------------------------------- /extension-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extension-app", 3 | "version": "1.0.0", 4 | "description": "chrome extension app use vue", 5 | "author": "no", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "lodash": "^4.17.10", 15 | "vue": "^2.5.2", 16 | "vue-router": "^3.0.1" 17 | }, 18 | "devDependencies": { 19 | "autoprefixer": "^7.1.2", 20 | "babel-core": "^6.22.1", 21 | "babel-eslint": "^8.2.1", 22 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 23 | "babel-loader": "^7.1.1", 24 | "babel-plugin-syntax-jsx": "^6.18.0", 25 | "babel-plugin-transform-runtime": "^6.22.0", 26 | "babel-plugin-transform-vue-jsx": "^3.5.0", 27 | "babel-preset-env": "^1.3.2", 28 | "babel-preset-stage-2": "^6.22.0", 29 | "chalk": "^2.0.1", 30 | "copy-webpack-plugin": "^4.0.1", 31 | "css-loader": "^0.28.0", 32 | "eslint": "^4.15.0", 33 | "eslint-config-standard": "^10.2.1", 34 | "eslint-friendly-formatter": "^3.0.0", 35 | "eslint-loader": "^1.7.1", 36 | "eslint-plugin-html": "^4.0.5", 37 | "eslint-plugin-import": "^2.7.0", 38 | "eslint-plugin-node": "^5.2.0", 39 | "eslint-plugin-promise": "^3.4.0", 40 | "eslint-plugin-standard": "^3.0.1", 41 | "eslint-plugin-vue": "^4.0.0", 42 | "extract-text-webpack-plugin": "^3.0.0", 43 | "file-loader": "^1.1.4", 44 | "friendly-errors-webpack-plugin": "^1.6.1", 45 | "html-webpack-plugin": "^2.30.1", 46 | "less": "^3.8.1", 47 | "less-loader": "^4.1.0", 48 | "node-notifier": "^5.1.2", 49 | "optimize-css-assets-webpack-plugin": "^3.2.0", 50 | "ora": "^1.2.0", 51 | "portfinder": "^1.0.13", 52 | "postcss-import": "^11.0.0", 53 | "postcss-loader": "^2.0.8", 54 | "postcss-url": "^7.2.1", 55 | "rimraf": "^2.6.0", 56 | "semver": "^5.3.0", 57 | "shelljs": "^0.7.6", 58 | "uglifyjs-webpack-plugin": "^1.1.1", 59 | "url-loader": "^0.5.8", 60 | "vue-loader": "^13.3.0", 61 | "vue-style-loader": "^3.0.1", 62 | "vue-template-compiler": "^2.5.2", 63 | "webpack": "^3.6.0", 64 | "webpack-bundle-analyzer": "^2.9.0", 65 | "webpack-dev-server": "^2.9.1", 66 | "webpack-merge": "^4.1.0" 67 | }, 68 | "engines": { 69 | "node": ">= 6.0.0", 70 | "npm": ">= 3.0.0" 71 | }, 72 | "browserslist": [ 73 | "> 1%", 74 | "last 2 versions", 75 | "not ie <= 8" 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /extension-app/build/webpack.prod_com.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const CopyWebpackPlugin = require('copy-webpack-plugin') 7 | const env = require('../config/prod.env') 8 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 9 | 10 | const webpackConfig = { 11 | entry: { 12 | 'contentScript/cs': './src/contentScript/cs.js', 13 | 'contentScript/runtime': './src/contentScript/runtime.js', 14 | 'background/bg': './src/background/bg.js' 15 | }, 16 | output: { 17 | path: config.build.assetsRoot, 18 | filename: './[name].js' 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.(js)$/, 24 | loader: 'eslint-loader', 25 | enforce: 'pre', 26 | include: [path.resolve(__dirname, '../src')], 27 | options: { 28 | formatter: require('eslint-friendly-formatter'), 29 | emitWarning: !config.dev.showEslintErrorsInOverlay, 30 | emitError: true 31 | } 32 | }, 33 | { 34 | test: /\.js$/, 35 | loader: 'babel-loader', 36 | include: [path.resolve(__dirname, '../src')] 37 | } 38 | ] 39 | }, 40 | devtool: false, 41 | plugins: [ 42 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 43 | new webpack.DefinePlugin({ 44 | 'process.env': env 45 | }), 46 | new UglifyJsPlugin({ 47 | uglifyOptions: { 48 | compress: { 49 | warnings: false 50 | } 51 | }, 52 | sourceMap: false, 53 | parallel: true 54 | }), 55 | // copy custom static assets 56 | new CopyWebpackPlugin([ 57 | { 58 | from: path.resolve(__dirname, '../src/manifest.json'), 59 | to: path.resolve(config.build.assetsRoot, 'manifest.json'), 60 | toType: 'file' 61 | }, 62 | { 63 | from: path.resolve(__dirname, '../src/assets/icon.png'), 64 | to: utils.assetsPath('../icons/icon.png'), 65 | toType: 'file' 66 | }, 67 | { 68 | from: path.resolve(__dirname, '../src/assets/conan-hand.png'), 69 | to: utils.assetsPath('../icons/conan-hand.png'), 70 | toType: 'file' 71 | }, 72 | { 73 | from: path.resolve(__dirname, '../static'), 74 | to: config.build.assetsSubDirectory, 75 | ignore: ['.*'] 76 | } 77 | ]) 78 | ] 79 | } 80 | 81 | module.exports = webpackConfig 82 | -------------------------------------------------------------------------------- /extension-app/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve(dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | const createLintingRule = () => ({ 12 | test: /\.(js|vue)$/, 13 | loader: 'eslint-loader', 14 | enforce: 'pre', 15 | include: [resolve('src'), resolve('test')], 16 | options: { 17 | formatter: require('eslint-friendly-formatter'), 18 | emitWarning: !config.dev.showEslintErrorsInOverlay 19 | } 20 | }) 21 | 22 | module.exports = { 23 | context: path.resolve(__dirname, '../'), 24 | entry: { 25 | app: './src/app/main.js', 26 | option: './src/option/main.js' 27 | }, 28 | output: { 29 | path: config.build.assetsRoot, 30 | filename: './js/[name].js', 31 | publicPath: process.env.NODE_ENV === 'production' ? 32 | config.build.assetsPublicPath : 33 | config.dev.assetsPublicPath 34 | }, 35 | resolve: { 36 | extensions: ['.js', '.vue', '.json'], 37 | alias: { 38 | 'vue$': 'vue/dist/vue.esm.js', 39 | '@': resolve('src'), 40 | } 41 | }, 42 | module: { 43 | rules: [ 44 | ...(config.dev.useEslint ? [createLintingRule()] : []), 45 | { 46 | test: /\.vue$/, 47 | loader: 'vue-loader', 48 | options: vueLoaderConfig 49 | }, 50 | { 51 | test: /\.js$/, 52 | loader: 'babel-loader', 53 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 54 | }, 55 | { 56 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 57 | loader: 'url-loader', 58 | options: { 59 | limit: 10000, 60 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 61 | } 62 | }, 63 | { 64 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 65 | loader: 'url-loader', 66 | options: { 67 | limit: 10000, 68 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 69 | } 70 | }, 71 | { 72 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 73 | loader: 'url-loader', 74 | options: { 75 | limit: 10000, 76 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 77 | } 78 | } 79 | ] 80 | }, 81 | node: { 82 | // prevent webpack from injecting useless setImmediate polyfill because Vue 83 | // source contains it (although only uses it if it's native). 84 | setImmediate: false, 85 | // prevent webpack from injecting mocks to Node native modules 86 | // that does not make sense for the client 87 | dgram: 'empty', 88 | fs: 'empty', 89 | net: 'empty', 90 | tls: 'empty', 91 | child_process: 'empty' 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /extension-app/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | const common = { 8 | // Paths 9 | assetsRoot: path.resolve(__dirname, '../crx'), 10 | assetsAppRoot: path.resolve(__dirname, '../crx/app'), 11 | assetsOptionRoot: path.resolve(__dirname, '../crx/option'), 12 | assetsSubDirectory: 'static', 13 | assetsPublicPath: '/', // '/'means absolute path; '' or './' means relative path. 14 | } 15 | 16 | // entries 17 | const entries = [{ 18 | name: 'app', 19 | src: './src/app/main.js', 20 | index: path.resolve(common.assetsAppRoot, 'index.html'), 21 | outPath: common.assetsAppRoot 22 | }, 23 | { 24 | name: 'option', 25 | src: './src/option/main.js', 26 | index: path.resolve(common.assetsOptionRoot, 'option.html'), 27 | outPath: common.assetsOptionRoot 28 | } 29 | ] 30 | 31 | module.exports = { 32 | dev: { 33 | ...common, 34 | 35 | entries, 36 | 37 | proxyTable: {}, 38 | 39 | // Various Dev Server settings 40 | host: 'localhost', // can be overwritten by process.env.HOST 41 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 42 | autoOpenBrowser: false, 43 | errorOverlay: true, 44 | notifyOnErrors: true, 45 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 46 | 47 | // Use Eslint Loader? 48 | // If true, your code will be linted during bundling and 49 | // linting errors and warnings will be shown in the console. 50 | useEslint: true, 51 | // If true, eslint errors and warnings will also be shown in the error overlay 52 | // in the browser. 53 | showEslintErrorsInOverlay: false, 54 | 55 | /** 56 | * Source Maps 57 | */ 58 | 59 | // https://webpack.js.org/configuration/devtool/#development 60 | devtool: 'cheap-module-eval-source-map', 61 | 62 | // If you have problems debugging vue-files in devtools, 63 | // set this to false - it *may* help 64 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 65 | cacheBusting: true, 66 | 67 | cssSourceMap: true 68 | }, 69 | 70 | build: { 71 | ...common, 72 | 73 | // Template for index.html 74 | entries, 75 | /** 76 | * Source Maps 77 | */ 78 | 79 | productionSourceMap: true, 80 | // https://webpack.js.org/configuration/devtool/#production 81 | devtool: '#source-map', 82 | 83 | // Gzip off by default as many popular static hosts such as 84 | // Surge or Netlify already gzip all static assets for you. 85 | // Before setting to `true`, make sure to: 86 | // npm install --save-dev compression-webpack-plugin 87 | productionGzip: false, 88 | productionGzipExtensions: ['js', 'css'], 89 | 90 | // Run the build command with an extra argument to 91 | // View the bundle analyzer report after build finishes: 92 | // `npm run build --report` 93 | // Set to `true` or `false` to always turn it on or off 94 | bundleAnalyzerReport: process.env.npm_config_report 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /extension-app/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' ? 9 | config.build.assetsSubDirectory : 10 | config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders(loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { 63 | indentedSyntax: true 64 | }), 65 | scss: generateLoaders('sass'), 66 | stylus: generateLoaders('stylus'), 67 | styl: generateLoaders('stylus') 68 | } 69 | } 70 | 71 | // Generate loaders for standalone style files (outside of .vue) 72 | exports.styleLoaders = function (options) { 73 | const output = [] 74 | const loaders = exports.cssLoaders(options) 75 | 76 | for (const extension in loaders) { 77 | const loader = loaders[extension] 78 | output.push({ 79 | test: new RegExp('\\.' + extension + '$'), 80 | use: loader 81 | }) 82 | } 83 | 84 | return output 85 | } 86 | 87 | exports.createNotifierCallback = () => { 88 | const notifier = require('node-notifier') 89 | 90 | return (severity, errors) => { 91 | if (severity !== 'error') return 92 | 93 | const error = errors[0] 94 | const filename = error.file && error.file.split('!').pop() 95 | 96 | notifier.notify({ 97 | title: packageConfig.name, 98 | message: severity + ': ' + error.name, 99 | subtitle: filename || '', 100 | icon: path.join(__dirname, 'logo.png') 101 | }) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /extension-app/src/contentScript/cs.js: -------------------------------------------------------------------------------- 1 | import MESSAGE from '../config/message'; 2 | class ContentScript { 3 | /** 4 | * 权限校验方法 5 | * @param {(string|regexp)} pattern 选项页填写的规则 6 | * @return {boolean} 是否校验通过 7 | */ 8 | checkPermission(pattern) { 9 | var url = location.href; 10 | pattern = pattern.replace(/(?:^\s+)|(?:\s+$)/, ''); 11 | if (pattern.match('/.*/')) { 12 | pattern = new RegExp(pattern.slice(1, pattern.length - 1)); 13 | return pattern.test(url); 14 | } 15 | return url.indexOf(pattern) > -1; 16 | } 17 | 18 | /** 19 | * 注入runtimejs 20 | * @param {string} styleText legoStickyTool的css代码 21 | * @ignore 22 | */ 23 | injectRuntimeJs(styleText) { 24 | let messageInput = document.createElement('input'); 25 | messageInput.type = 'hidden'; 26 | messageInput.id = 'extensionMessageJson'; 27 | messageInput.value = JSON.stringify(MESSAGE); 28 | document.body.append(messageInput); 29 | 30 | let runtimeJs = document.createElement('script'); 31 | runtimeJs.src = window.chrome.extension.getURL('contentScript/runtime.js'); 32 | 33 | document.head.append(runtimeJs); 34 | } 35 | 36 | bindEvents() { 37 | // 和popup的交互 38 | window.chrome.runtime.onMessage.addListener(request => { 39 | switch (request.code) { 40 | case MESSAGE.INIT: 41 | // 收到popup初始化请求,告诉页面初始化处理 42 | window.postMessage({ code: MESSAGE.INIT }, '*'); 43 | break; 44 | 45 | case MESSAGE.SET_VALUE: 46 | // 收到popup操作页面对象请求,向当前chrome tab 页面传递操作信息 47 | window.postMessage({ 48 | code: MESSAGE.SET_VALUE_ON_PAGE, 49 | name: request.name, 50 | value: request.value, 51 | index: request.index 52 | }, '*'); 53 | break; 54 | 55 | default: 56 | break; 57 | } 58 | }, false); 59 | 60 | // 与当前chrome tab页面的交互 61 | window.addEventListener('message', function (e) { 62 | if (e.data && e.data.code) { 63 | switch (e.data.code) { 64 | case MESSAGE.NODATA_ON_PAGE: 65 | window.chrome.runtime.sendMessage({ 66 | code: MESSAGE.NODATA 67 | }); 68 | break; 69 | 70 | case MESSAGE.RECEIVE_ERROR_ON_PAGE: 71 | // 页面sdk操作失败消息处理 72 | window.chrome.runtime.sendMessage({ 73 | code: MESSAGE.RECEIVE_ERROR, 74 | message: e.data.message 75 | }); 76 | break; 77 | 78 | default: 79 | break; 80 | } 81 | } 82 | }); 83 | } 84 | 85 | async run() { 86 | // 获取插件权限from bg.js,注入runtime.js 87 | window.chrome.runtime.sendMessage({code: MESSAGE.GET_PERMISSION_RULES}, response => { 88 | if (!response) { 89 | return; 90 | } 91 | 92 | let permissions = response.permission.replace(/(\r\n)|\r|\n/g, ',').split(','); 93 | permissions.forEach(permission => { 94 | if (permission && this.checkPermission(permission)) { 95 | window.chrome.runtime.sendMessage({code: MESSAGE.RECEIVE_PERMISSION}); 96 | this.bindEvents(); 97 | this.injectRuntimeJs(); 98 | return false; 99 | } 100 | }); 101 | }); 102 | } 103 | } 104 | 105 | let cs = new ContentScript(); 106 | cs.run(); 107 | -------------------------------------------------------------------------------- /extension-app/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | const lodash_1 = require('lodash') 13 | 14 | const HOST = process.env.HOST 15 | const PORT = process.env.PORT && Number(process.env.PORT) 16 | 17 | const devWebpackConfig = merge(baseWebpackConfig, { 18 | module: { 19 | rules: utils.styleLoaders({ 20 | sourceMap: config.dev.cssSourceMap, 21 | usePostCSS: true 22 | }) 23 | }, 24 | // cheap-module-eval-source-map is faster for development 25 | devtool: config.dev.devtool, 26 | 27 | // these devServer options should be customized in /config/index.js 28 | devServer: { 29 | clientLogLevel: 'warning', 30 | historyApiFallback: { 31 | rewrites: [{ 32 | from: /.*/, 33 | to: path.posix.join(config.dev.assetsPublicPath, 'index.html') 34 | }, ], 35 | }, 36 | hot: true, 37 | contentBase: false, // since we use CopyWebpackPlugin. 38 | compress: true, 39 | host: HOST || config.dev.host, 40 | port: PORT || config.dev.port, 41 | open: config.dev.autoOpenBrowser, 42 | overlay: config.dev.errorOverlay ? 43 | { 44 | warnings: false, 45 | errors: true 46 | } : 47 | false, 48 | publicPath: config.dev.assetsPublicPath, 49 | proxy: config.dev.proxyTable, 50 | quiet: true, // necessary for FriendlyErrorsPlugin 51 | watchOptions: { 52 | poll: config.dev.poll, 53 | } 54 | }, 55 | plugins: [ 56 | new webpack.DefinePlugin({ 57 | 'process.env': require('../config/dev.env') 58 | }), 59 | new webpack.HotModuleReplacementPlugin(), 60 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 61 | new webpack.NoEmitOnErrorsPlugin(), 62 | // https://github.com/ampedandwired/html-webpack-plugin 63 | ...lodash_1.map(config.dev.entries, entry => new HtmlWebpackPlugin({ 64 | filename: `${entry.name}/index.html`, //sign page url 65 | template: `entry/${entry.name}.html`, 66 | chunks: [`${entry.name}`], 67 | inject: true 68 | })), 69 | // copy custom static assets 70 | new CopyWebpackPlugin([{ 71 | from: path.resolve(__dirname, '../static'), 72 | to: config.dev.assetsSubDirectory, 73 | ignore: ['.*'] 74 | }]) 75 | ] 76 | }) 77 | 78 | module.exports = new Promise((resolve, reject) => { 79 | portfinder.basePort = process.env.PORT || config.dev.port 80 | portfinder.getPort((err, port) => { 81 | if (err) { 82 | reject(err) 83 | } else { 84 | // publish the new Port, necessary for e2e tests 85 | process.env.PORT = port 86 | // add port to devServer config 87 | devWebpackConfig.devServer.port = port 88 | 89 | // Add FriendlyErrorsPlugin 90 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 91 | compilationSuccessInfo: { 92 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 93 | }, 94 | onErrors: config.dev.notifyOnErrors ? 95 | utils.createNotifierCallback() : 96 | undefined 97 | })) 98 | 99 | resolve(devWebpackConfig) 100 | } 101 | }) 102 | }) 103 | -------------------------------------------------------------------------------- /extension-app/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = require('../config/prod.env') 15 | 16 | function webpackConfig(entry) { 17 | const prodConfig = merge(baseWebpackConfig, { 18 | module: { 19 | rules: utils.styleLoaders({ 20 | sourceMap: config.build.productionSourceMap, 21 | extract: true, 22 | usePostCSS: true 23 | }) 24 | }, 25 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 26 | entry: {[entry.name]: entry.src}, 27 | output: { 28 | path: entry.outPath, 29 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 30 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js'), 31 | publicPath: `/${entry.name}/` 32 | }, 33 | plugins: [ 34 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 35 | new webpack.DefinePlugin({ 36 | 'process.env': env 37 | }), 38 | new UglifyJsPlugin({ 39 | uglifyOptions: { 40 | compress: { 41 | warnings: false 42 | } 43 | }, 44 | sourceMap: config.build.productionSourceMap, 45 | parallel: true 46 | }), 47 | // extract css into its own file 48 | new ExtractTextPlugin({ 49 | filename: utils.assetsPath('css/[name].[contenthash].css'), 50 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 51 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 52 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 53 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 54 | allChunks: true, 55 | }), 56 | // Compress extracted CSS. We are using this plugin so that possible 57 | // duplicated CSS from different components can be deduped. 58 | new OptimizeCSSPlugin({ 59 | cssProcessorOptions: config.build.productionSourceMap ? 60 | { 61 | safe: true, 62 | map: { 63 | inline: false 64 | } 65 | } : 66 | { 67 | safe: true 68 | } 69 | }), 70 | // generate dist index.html with correct asset hash for caching. 71 | // you can customize output by editing /index.html 72 | // see https://github.com/ampedandwired/html-webpack-plugin 73 | new HtmlWebpackPlugin({ 74 | filename: entry.index, 75 | template: `entry/${entry.name}.html`, 76 | inject: true, 77 | minify: { 78 | removeComments: true, 79 | collapseWhitespace: true, 80 | removeAttributeQuotes: true 81 | // more options: 82 | // https://github.com/kangax/html-minifier#options-quick-reference 83 | }, 84 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 85 | chunksSortMode: 'dependency' 86 | }), 87 | // keep module.id stable when vendor modules does not change 88 | new webpack.HashedModuleIdsPlugin(), 89 | // enable scope hoisting 90 | new webpack.optimize.ModuleConcatenationPlugin(), 91 | // split vendor js into its own file 92 | new webpack.optimize.CommonsChunkPlugin({ 93 | name: 'vendor', 94 | minChunks(module) { 95 | // any required modules inside node_modules are extracted to vendor 96 | return ( 97 | module.resource && 98 | /\.js$/.test(module.resource) && 99 | module.resource.indexOf( 100 | path.join(__dirname, '../node_modules') 101 | ) === 0 102 | ) 103 | } 104 | }), 105 | // extract webpack runtime and module manifest to its own file in order to 106 | // prevent vendor hash from being updated whenever app bundle is updated 107 | new webpack.optimize.CommonsChunkPlugin({ 108 | name: 'manifest', 109 | minChunks: Infinity 110 | }), 111 | // This instance extracts shared chunks from code splitted chunks and bundles them 112 | // in a separate chunk, similar to the vendor chunk 113 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 114 | new webpack.optimize.CommonsChunkPlugin({ 115 | name: 'app', 116 | async: 'vendor-async', 117 | children: true, 118 | minChunks: 3 119 | }) 120 | ] 121 | }) 122 | 123 | if (config.build.productionGzip) { 124 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 125 | prodConfig.plugins.push( 126 | new CompressionWebpackPlugin({ 127 | asset: '[path].gz[query]', 128 | algorithm: 'gzip', 129 | test: new RegExp( 130 | '\\.(' + 131 | config.build.productionGzipExtensions.join('|') + 132 | ')$' 133 | ), 134 | threshold: 10240, 135 | minRatio: 0.8 136 | }) 137 | ) 138 | } 139 | 140 | if (config.build.bundleAnalyzerReport) { 141 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 142 | prodConfig.plugins.push(new BundleAnalyzerPlugin()) 143 | } 144 | 145 | return prodConfig; 146 | } 147 | 148 | module.exports = webpackConfig 149 | --------------------------------------------------------------------------------