Repository: senghoo/wordai Branch: master Commit: 2210cc558409 Files: 61 Total size: 92.3 KB Directory structure: gitextract_fuu3vrhz/ ├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app.py ├── commands/ │ └── __init__.py ├── config/ │ ├── __init__.py │ └── wsgi.ini ├── data/ │ ├── __init__.py │ └── dict.py ├── jsondict/ │ ├── anki.py │ └── search.py ├── manage.py ├── nginx-server.conf ├── nginx.conf ├── requirements.txt ├── start.sh ├── ui/ │ ├── .babelrc │ ├── .editorconfig │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .postcssrc.js │ ├── README.md │ ├── build/ │ │ ├── build.js │ │ ├── check-versions.js │ │ ├── utils.js │ │ ├── vue-loader.conf.js │ │ ├── webpack.base.conf.js │ │ ├── webpack.dev.conf.js │ │ └── webpack.prod.conf.js │ ├── config/ │ │ ├── dev.env.js │ │ ├── index.js │ │ └── prod.env.js │ ├── index.html │ ├── package.json │ ├── src/ │ │ ├── App.vue │ │ ├── components/ │ │ │ ├── Card.vue │ │ │ ├── Dictionary.vue │ │ │ ├── Login.vue │ │ │ ├── Main.vue │ │ │ ├── statistic/ │ │ │ │ ├── DayChart.vue │ │ │ │ └── index.vue │ │ │ └── wordlist/ │ │ │ ├── Form.vue │ │ │ ├── Learned.vue │ │ │ ├── List.vue │ │ │ ├── Setting.vue │ │ │ ├── ToLearn.vue │ │ │ └── index.vue │ │ ├── main.js │ │ ├── permission.js │ │ ├── request.js │ │ ├── router/ │ │ │ └── index.js │ │ └── store.js │ └── static/ │ └── .gitkeep └── wordai/ ├── __init__.py ├── api/ │ ├── __init__.py │ └── apis.py └── models/ ├── __init__.py └── models.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ ui/node_modules ================================================ FILE: .gitignore ================================================ ### https://raw.github.com/github/gitignore/f9291de89f5f7dc0d3d87f9eb111b839f81d5dbc/Global/Emacs.gitignore # -*- mode: gitignore; -*- *~ \#*\# /.emacs.desktop /.emacs.desktop.lock *.elc auto-save-list tramp .\#* # Org-mode .org-id-locations *_archive # flymake-mode *_flymake.* # eshell files /eshell/history /eshell/lastdir # elpa packages /elpa/ # reftex files *.rel # AUCTeX auto folder /auto/ # cask packages .cask/ dist/ # Flycheck flycheck_*.el # server auth directory /server/ # projectiles files .projectile # directory configuration .dir-locals.el # network security /network-security.data ### https://raw.github.com/github/gitignore/f9291de89f5f7dc0d3d87f9eb111b839f81d5dbc/Python.gitignore # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ /data/data/ .DS_Store */.DS_Store ================================================ FILE: Dockerfile ================================================ FROM python:3.7 RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - RUN apt-get update && apt-get install -y\ build-essential libpq-dev nodejs nginx-extras ENV NODE_PATH /usr/lib/node_modules RUN pip install uwsgi WORKDIR /usr/src/app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY ui/package.json ./ui/ WORKDIR /usr/src/app/ui RUN npm install COPY . /usr/src/app ENV NODE_ENV production RUN npm run build WORKDIR /usr/src/app COPY nginx.conf /etc/nginx/ COPY nginx-server.conf /etc/nginx/sites-enabled/default EXPOSE 80 CMD './start.sh' ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Senghoo Kim Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Word AI 一款使用自然语言处理辅助背单词的程序。 通过导入TMX翻译语料库,进行分词、词素分析、单词还原等操作自动生成各种不同语态、情态下的英语填空来进行单词学习。 *PS: 因为版权原因,不提供词典文件和TMX语料文件。需要自行下载导入。业余项目,分享出来供学习和交流,提供有限的技术支持,如有疑问欢迎Issue。* ## 功能特色 * 根据中文填写英文句子中的单词。 * 根据语料库自动生成题目,*拒绝重复* * 根据艾宾浩斯记忆曲线重复出现需要复习的单词。 * 对于错误单词自动弹出词典,进行学习。 * 支持自定义词库。 * B/S架构,兼容移动端和PC端。 ## 个人对背单词的一点看法 1. 背单词不能只针对独立的单词,需要放到语境中学习。 2. 单词需要自己完整的拼写出来,不能是只进行选择题。不然实际应用中容易出现拼写错误的情况。 3. 单词记忆时要关注时态、语态。 4. 学习的内容不能重复,也就是每次学习的上下文要发生改变,不能是固定的句子,学习固定的单词。这种情况下容易脱离了当前上下文,还是想不起单词。 ## Examples ### 单词学习 如遇到不会的单词,可以长按空格查看答案。  对于错误单词自动弹出词典以供学习。  ### 自定义单词列表 文本框输入单词列表。一行一个,自动提示词典查询结果,以检查是否添加正确。  ### 学习统计 提供简单的统计分析功能。  ## 管理命令 因为是简单自用为目的设计的软件,没有单独做管理页面。系统管理通过命令行进行。 添加用户 ``` # 普通用户 python manage.py useradd <用户名> <口令> # 管理员 python manage.py useradd <用户名> <口令> admin ``` 导入字典 ``` python manage.py sync_dict ``` 导入例句 ``` python manage.py sync_sentence ``` 分析词典 ``` python manage.py wordlist # 标注 单词星级 python manage.py dict_parse # 拆分中英文解释 ``` ================================================ FILE: app.py ================================================ from wordai.api import app from config import api_env, flask_config app.config.update(flask_config()) def run_dev(): app.run(**api_env()) ================================================ FILE: commands/__init__.py ================================================ import string from data import load_dict, load_sentence from data.dict import Description from wordai.models import Word, Sentence, User, WordList, DictDescInfo def dict_to_mongo(): items = load_dict() for word, item in items.items(): if Word.objects(word=word).count() == 0: dbitem = Word(**item) dbitem.save() def sentence_to_mongo(*typ): items = load_sentence(*typ) print(items) for k, v in items.items(): _sentence_to_mongo(k, v) def _sentence_to_mongo(typ, items): import nltk from nltk.corpus import wordnet def wordnet_pos(tag): if tag.startswith('J'): return wordnet.ADJ elif tag.startswith('V'): return wordnet.VERB elif tag.startswith('N'): return wordnet.NOUN elif tag.startswith('R'): return wordnet.ADV else: return wordnet.NOUN # nltk.download('punkt') nltk.download('averaged_perceptron_tagger') nltk.download('stopwords') nltk.download('wordnet') nltk.download('punkt') stop_words = set(nltk.corpus.stopwords.words('english')) stemmer = nltk.stem.WordNetLemmatizer() sentences = [] for trans in items: eng, chn = trans.getsource(), trans.gettarget() tokens = nltk.word_tokenize(eng) pos_tag = [pos[1] for pos in nltk.pos_tag(tokens)] roots = [stemmer.lemmatize(word, wordnet_pos(pos_tag[idx])) for idx, word in enumerate(tokens)] cleanword = [token for token in roots if token.isalpha() and token not in stop_words and len(token) >= 3] # remove duplicates clean_word = list(dict.fromkeys(cleanword)) if len(clean_word) > 0: score = Word.search_words(*clean_word).sum('star') / len(clean_word) else: score = -1 sentence = Sentence(eng=eng, chn=chn, words=tokens, pos_tag=pos_tag, roots=roots, score=score, typ=typ) sentences.append(sentence) if len(sentences) > 50: Sentence.objects.insert(sentences) sentences = [] def new_user(username, passwd, role='user', *args): u = User(username=username, password=passwd, role=role) u.save() def dict_star_word_list(): for star in range(1, 6, 1): words = [word.word for word in Word.objects(star=star).only('word')] wl = WordList(name="word star {}".format(star), words=words) wl.save() def dict_parse(): for word in Word.objects: for des in word.descriptions: res = Description(des.description) des.seq = res.seq des.cn = res.cn des.en = res.en des.speech = res.speech des.infos = [DictDescInfo(**info) for info in res.info] word.save() def run(method, *args): if method == "sync_dict": dict_to_mongo() elif method == "sync_sentence": sentence_to_mongo(*args) elif method == "useradd": new_user(*args) elif method == "wordlist": dict_star_word_list(*args) elif method == "dict_parse": dict_parse(*args) else: print("unknown command {0}".format(method)) ================================================ FILE: config/__init__.py ================================================ import os def c(name, default=None): return os.environ.get(name.upper(), default) def mongo_config(): return { 'host': c('mongo_host', "127.0.0.1"), 'port': int(c('mongo_port', "27017")), 'db': c('mongo_db', "wordai"), 'username': c('mongo_username', "root"), 'password': c('mongo_password', ""), } def api_env(): return { 'host': c('host', '127.0.0.1'), 'port': int(c('port', 8000)), 'debug': c('debug', "true") == "true" } def flask_config(): return { 'JWT_SECRET_KEY': c('secret', '__SOME_SECRET_KEYS_HERE__') } ================================================ FILE: config/wsgi.ini ================================================ [uwsgi] module = app:app master = true processes = 3 socket = /var/run/wordai.sock logto = /var/log/wordai.log chmod-socket = 660 vacuum = true ================================================ FILE: data/__init__.py ================================================ # -*- coding: utf-8 -*- import os import json from itertools import chain from translate.storage.tmx import tmxfile DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data") def data_file(filename): return os.path.join(DATA_DIR, filename) def load_dict(): with open(data_file("dict.json"), "r") as f: return json.load(f) def load_sentence(*typ): files = { 'talks': ['日常口语_20190906111009_1.tmx', '日常口语_20190906111009_2.tmx', '日常口语_20190906111009_3.tmx'], 'dictexams': ['词典例句汇集1.tmx', '词典例句汇集3.tmx', '词典例句汇集5.tmx', '词典例句汇集7.tmx', '词典例句汇集2.tmx', '词典例句汇集4.tmx', '词典例句汇集6.tmx', '词典例句汇集8.tmx'] } iters = {} for t, fs in files.items(): if len(typ) == 0 or t in typ: type_iterns = [] for fname in fs: with open(data_file(fname), 'rb') as fin: tmx = tmxfile(fin, 'en', 'cn') type_iterns.append(tmx.unit_iter()) iters[t] = chain(*type_iterns) return iters ================================================ FILE: data/dict.py ================================================ # -*- coding: utf-8 -*- import string import re class Description(object): def __init__(self, des): self.des = des self.idx = 0 self.length = len(des) self.info = [] if not self.read_seq(): self.seq = 0 self.en = self.des[self.idx:] self.cn = self.des[self.idx:] self.speech = 'OTHER' return if not self.read_word_speech(): self.en = self.des[self.idx:] self.cn = self.des[self.idx:] self.speech = 'OTHER' return if not self.read_cn_and_en(): self.en = self.des[self.idx:] self.cn = self.des[self.idx:] return self.read_info() def read_seq(self): res = '' while(self.idx < self.length): if self.des[self.idx] <= '9' and self.des[self.idx] >= '0': res += self.des[self.idx] self.idx += 1 else: break if self.des[self.idx] == '.': self.idx += 1 if len(res) > 0: self.seq = int(res.strip()) return True return False def read_word_speech(self): res = '' while(self.idx < self.length): if self.des[self.idx] in string.ascii_uppercase+"- ;": res += self.des[self.idx] self.idx += 1 else: break res = res.strip() if self.des[self.idx] == '\t': self.idx += 1 if len(res) > 0: self.speech = res return True return False def read_cn_and_en(self): end = self.des.find('【', self.idx) if end == -1: end = len(self.des) idx = end while(idx >= self.idx): idx -= 1 if self.des[idx] not in string.printable: break if idx == self.idx: self.cn = '' self.en = self.des[idx:end] elif idx == end-1: self.cn = self.des[idx:end] self.en = self.des[idx:end] else: self.cn = self.des[self.idx:idx+1] self.en = self.des[idx+1:end] self.idx = end return True def read_info(self): text = self.des[self.idx:] res = re.findall(r'【([^【】]+)】:([^【】]+)', text, re.M) for item in res: self.info.append({ 'name': item[0], 'value': item[1], }) ================================================ FILE: jsondict/anki.py ================================================ import sys import csv from search import search_string, description res = [] def cloze_word(sentense, word, level="1"): return sentense.lower().replace(word, "{{c"+level+"::"+word+"}}") with open(sys.argv[1], 'r') as f: contents = f.read() items = contents.split("\n\n") for item in items: lines = item.split("\n") word = lines[0] exp = lines[1] eg = lines[2:] dct = search_string(word) exp2 = cloze_word(description(word), word, "1") for x in [{"sentence": cloze_word(x, word, "1"),"exp2": exp2, "exp": exp, "dict": dct } for x in eg]: res.append(x) with open('{0}.csv'.format(sys.argv[1]), 'w') as csv_file: fieldnames = ['sentence','exp2', 'exp', 'dict'] writer = csv.DictWriter(csv_file, fieldnames=fieldnames) for row in res: print(row['sentence']) writer.writerows(res) ================================================ FILE: jsondict/search.py ================================================ #!/usr/bin/python # -*- coding: utf-8 -*- import sys import json with open('dict.json', 'r') as f: dictionary = json.load(f) def search(word): try: return dictionary[word] except: return None def format(item): res = "{word}\tStar:{star}\n".format(**item) for desc in item['descriptions']: res += "\t{description}\n".format(**desc) for ex in desc['examples']: res += "\t\tEN:\t{en}\n\t\tCN:\t{cn}\n".format(**ex) res +="\n" return res def description(word): item = search(word) if item is None: return "" return "\n".join([x['description'] for x in item['descriptions']]) def search_string(word): res = search(word) if res is None: return "" return format(res) if __name__ == '__main__': res = search(sys.argv[1]) if res is not None: print(format(res)) ================================================ FILE: manage.py ================================================ import os import sys ROOT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, ROOT_DIR) import commands import app def command(method, *args): if method == 'task': commands.run(*args) elif method == 'dev_server': app.run_dev() else: print("unknown command {0}".format(method)) def usage(): print("{0} task".format( sys.argv[0])) if __name__ == '__main__': if len(sys.argv) > 1: command(*sys.argv[1:]) else: usage() ================================================ FILE: nginx-server.conf ================================================ server { listen 80; server_name 0.0.0.0; location /api/ { include uwsgi_params; uwsgi_pass unix:/var/run/wordai.sock; } location / { root /usr/src/app/ui/dist/; } } ================================================ FILE: nginx.conf ================================================ user root; worker_processes auto; pid /run/nginx.pid; include /etc/nginx/modules-enabled/*.conf; events { worker_connections 768; } http { sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; gzip on; include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; } ================================================ FILE: requirements.txt ================================================ mongoengine mongoengine-goodjson lxml translate-toolkit nltk flask flask-restful flask-jwt-extended flask-cors bcrypt jsonschema ================================================ FILE: start.sh ================================================ #!/bin/bash service nginx start uwsgi --ini config/wsgi.ini ================================================ FILE: ui/.babelrc ================================================ { "presets": [ ["env", { "modules": false, "targets": { "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] } }], "stage-2" ], "plugins": ["transform-vue-jsx", "transform-runtime"] } ================================================ FILE: ui/.editorconfig ================================================ root = true [*] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true ================================================ FILE: ui/.eslintignore ================================================ /build/ /config/ /dist/ /*.js ================================================ FILE: ui/.eslintrc.js ================================================ // https://eslint.org/docs/user-guide/configuring module.exports = { root: true, parserOptions: { parser: 'babel-eslint' }, env: { browser: true, }, extends: [ // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 'plugin:vue/essential', // https://github.com/standard/standard/blob/master/docs/RULES-en.md 'standard' ], // required to lint *.vue files plugins: [ 'vue' ], // add your custom rules here rules: { // allow async-await 'generator-star-spacing': 'off', // allow debugger during development 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' } } ================================================ FILE: ui/.gitignore ================================================ .DS_Store node_modules/ /dist/ npm-debug.log* yarn-debug.log* yarn-error.log* # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln ================================================ FILE: ui/.postcssrc.js ================================================ // https://github.com/michael-ciniawsky/postcss-load-config module.exports = { "plugins": { "postcss-import": {}, "postcss-url": {}, // to edit target browsers: use "browserslist" field in package.json "autoprefixer": {} } } ================================================ FILE: ui/README.md ================================================ # wordai > Word AI ## Build Setup ``` bash # install dependencies npm install # serve with hot reload at localhost:8080 npm run dev # build for production with minification npm run build # build for production and view the bundle analyzer report npm run build --report ``` 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). ================================================ FILE: ui/build/build.js ================================================ 'use strict' require('./check-versions')() process.env.NODE_ENV = 'production' const ora = require('ora') const rm = require('rimraf') const path = require('path') const chalk = require('chalk') const webpack = require('webpack') const config = require('../config') const webpackConfig = require('./webpack.prod.conf') const spinner = ora('building for production...') spinner.start() rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { if (err) throw err webpack(webpackConfig, (err, stats) => { spinner.stop() if (err) throw err process.stdout.write(stats.toString({ colors: true, modules: false, children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. chunks: false, chunkModules: false }) + '\n\n') if (stats.hasErrors()) { console.log(chalk.red(' Build failed with errors.\n')) process.exit(1) } console.log(chalk.cyan(' Build complete.\n')) console.log(chalk.yellow( ' Tip: built files are meant to be served over an HTTP server.\n' + ' Opening index.html over file:// won\'t work.\n' )) }) }) ================================================ FILE: ui/build/check-versions.js ================================================ 'use strict' const chalk = require('chalk') const semver = require('semver') const packageConfig = require('../package.json') const shell = require('shelljs') function exec (cmd) { return require('child_process').execSync(cmd).toString().trim() } const versionRequirements = [ { name: 'node', currentVersion: semver.clean(process.version), versionRequirement: packageConfig.engines.node } ] if (shell.which('npm')) { versionRequirements.push({ name: 'npm', currentVersion: exec('npm --version'), versionRequirement: packageConfig.engines.npm }) } module.exports = function () { const warnings = [] for (let i = 0; i < versionRequirements.length; i++) { const mod = versionRequirements[i] if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { warnings.push(mod.name + ': ' + chalk.red(mod.currentVersion) + ' should be ' + chalk.green(mod.versionRequirement) ) } } if (warnings.length) { console.log('') console.log(chalk.yellow('To use this template, you must update following to modules:')) console.log() for (let i = 0; i < warnings.length; i++) { const warning = warnings[i] console.log(' ' + warning) } console.log() process.exit(1) } } ================================================ FILE: ui/build/utils.js ================================================ 'use strict' const path = require('path') const config = require('../config') const ExtractTextPlugin = require('extract-text-webpack-plugin') const packageConfig = require('../package.json') exports.assetsPath = function (_path) { const assetsSubDirectory = process.env.NODE_ENV === 'production' ? config.build.assetsSubDirectory : config.dev.assetsSubDirectory return path.posix.join(assetsSubDirectory, _path) } exports.cssLoaders = function (options) { options = options || {} const cssLoader = { loader: 'css-loader', options: { sourceMap: options.sourceMap } } const postcssLoader = { loader: 'postcss-loader', options: { sourceMap: options.sourceMap } } // generate loader string to be used with extract text plugin function generateLoaders (loader, loaderOptions) { const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] if (loader) { loaders.push({ loader: loader + '-loader', options: Object.assign({}, loaderOptions, { sourceMap: options.sourceMap }) }) } // Extract CSS when that option is specified // (which is the case during production build) if (options.extract) { return ExtractTextPlugin.extract({ use: loaders, fallback: 'vue-style-loader' }) } else { return ['vue-style-loader'].concat(loaders) } } // https://vue-loader.vuejs.org/en/configurations/extract-css.html return { css: generateLoaders(), postcss: generateLoaders(), less: generateLoaders('less'), sass: generateLoaders('sass', { indentedSyntax: true }), scss: generateLoaders('sass'), stylus: generateLoaders('stylus'), styl: generateLoaders('stylus') } } // Generate loaders for standalone style files (outside of .vue) exports.styleLoaders = function (options) { const output = [] const loaders = exports.cssLoaders(options) for (const extension in loaders) { const loader = loaders[extension] output.push({ test: new RegExp('\\.' + extension + '$'), use: loader }) } return output } exports.createNotifierCallback = () => { const notifier = require('node-notifier') return (severity, errors) => { if (severity !== 'error') return const error = errors[0] const filename = error.file && error.file.split('!').pop() notifier.notify({ title: packageConfig.name, message: severity + ': ' + error.name, subtitle: filename || '', icon: path.join(__dirname, 'logo.png') }) } } ================================================ FILE: ui/build/vue-loader.conf.js ================================================ 'use strict' const utils = require('./utils') const config = require('../config') const isProduction = process.env.NODE_ENV === 'production' const sourceMapEnabled = isProduction ? config.build.productionSourceMap : config.dev.cssSourceMap module.exports = { loaders: utils.cssLoaders({ sourceMap: sourceMapEnabled, extract: isProduction }), cssSourceMap: sourceMapEnabled, cacheBusting: config.dev.cacheBusting, transformToRequire: { video: ['src', 'poster'], source: 'src', img: 'src', image: 'xlink:href' } } ================================================ FILE: ui/build/webpack.base.conf.js ================================================ 'use strict' const path = require('path') const utils = require('./utils') const config = require('../config') const vueLoaderConfig = require('./vue-loader.conf') function resolve (dir) { return path.join(__dirname, '..', dir) } const createLintingRule = () => ({ test: /\.(js|vue)$/, loader: 'eslint-loader', enforce: 'pre', include: [resolve('src'), resolve('test')], options: { formatter: require('eslint-friendly-formatter'), emitWarning: !config.dev.showEslintErrorsInOverlay } }) module.exports = { context: path.resolve(__dirname, '../'), entry: { app: './src/main.js' }, output: { path: config.build.assetsRoot, filename: '[name].js', publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath }, resolve: { extensions: ['.js', '.vue', '.json'], alias: { 'vue$': 'vue/dist/vue.esm.js', '@': resolve('src'), } }, module: { rules: [ ...(config.dev.useEslint ? [createLintingRule()] : []), { test: /\.vue$/, loader: 'vue-loader', options: vueLoaderConfig }, { test: /\.s[a|c]ss$/, loader: 'style!css!sass' }, { test: /\.js$/, loader: 'babel-loader', include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } }, { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('media/[name].[hash:7].[ext]') } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('fonts/[name].[hash:7].[ext]') } } ] }, node: { // prevent webpack from injecting useless setImmediate polyfill because Vue // source contains it (although only uses it if it's native). setImmediate: false, // prevent webpack from injecting mocks to Node native modules // that does not make sense for the client dgram: 'empty', fs: 'empty', net: 'empty', tls: 'empty', child_process: 'empty' } } ================================================ FILE: ui/build/webpack.dev.conf.js ================================================ 'use strict' const utils = require('./utils') const webpack = require('webpack') const config = require('../config') const merge = require('webpack-merge') const path = require('path') const baseWebpackConfig = require('./webpack.base.conf') const CopyWebpackPlugin = require('copy-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') const portfinder = require('portfinder') const HOST = process.env.HOST const PORT = process.env.PORT && Number(process.env.PORT) const devWebpackConfig = merge(baseWebpackConfig, { module: { rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) }, // cheap-module-eval-source-map is faster for development devtool: config.dev.devtool, // these devServer options should be customized in /config/index.js devServer: { clientLogLevel: 'warning', historyApiFallback: { rewrites: [ { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, ], }, hot: true, contentBase: false, // since we use CopyWebpackPlugin. compress: true, host: HOST || config.dev.host, port: PORT || config.dev.port, open: config.dev.autoOpenBrowser, overlay: config.dev.errorOverlay ? { warnings: false, errors: true } : false, publicPath: config.dev.assetsPublicPath, proxy: config.dev.proxyTable, quiet: true, // necessary for FriendlyErrorsPlugin watchOptions: { poll: config.dev.poll, } }, plugins: [ new webpack.DefinePlugin({ 'process.env': require('../config/dev.env') }), new webpack.HotModuleReplacementPlugin(), new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. new webpack.NoEmitOnErrorsPlugin(), // https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: 'index.html', template: 'index.html', inject: true }), // copy custom static assets new CopyWebpackPlugin([ { from: path.resolve(__dirname, '../static'), to: config.dev.assetsSubDirectory, ignore: ['.*'] } ]) ] }) module.exports = new Promise((resolve, reject) => { portfinder.basePort = process.env.PORT || config.dev.port portfinder.getPort((err, port) => { if (err) { reject(err) } else { // publish the new Port, necessary for e2e tests process.env.PORT = port // add port to devServer config devWebpackConfig.devServer.port = port // Add FriendlyErrorsPlugin devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ compilationSuccessInfo: { messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], }, onErrors: config.dev.notifyOnErrors ? utils.createNotifierCallback() : undefined })) resolve(devWebpackConfig) } }) }) ================================================ FILE: ui/build/webpack.prod.conf.js ================================================ 'use strict' const path = require('path') const utils = require('./utils') const webpack = require('webpack') const config = require('../config') const merge = require('webpack-merge') const baseWebpackConfig = require('./webpack.base.conf') const CopyWebpackPlugin = require('copy-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const ExtractTextPlugin = require('extract-text-webpack-plugin') const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') const UglifyJsPlugin = require('uglifyjs-webpack-plugin') const env = require('../config/prod.env') const webpackConfig = merge(baseWebpackConfig, { module: { rules: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true, usePostCSS: true }) }, devtool: config.build.productionSourceMap ? config.build.devtool : false, output: { path: config.build.assetsRoot, filename: utils.assetsPath('js/[name].[chunkhash].js'), chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') }, plugins: [ // http://vuejs.github.io/vue-loader/en/workflow/production.html new webpack.DefinePlugin({ 'process.env': env }), new UglifyJsPlugin({ uglifyOptions: { compress: { warnings: false } }, sourceMap: config.build.productionSourceMap, parallel: true }), // extract css into its own file new ExtractTextPlugin({ filename: utils.assetsPath('css/[name].[contenthash].css'), // Setting the following option to `false` will not extract CSS from codesplit chunks. // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 allChunks: true, }), // Compress extracted CSS. We are using this plugin so that possible // duplicated CSS from different components can be deduped. new OptimizeCSSPlugin({ cssProcessorOptions: config.build.productionSourceMap ? { safe: true, map: { inline: false } } : { safe: true } }), // generate dist index.html with correct asset hash for caching. // you can customize output by editing /index.html // see https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: config.build.index, template: 'index.html', inject: true, minify: { removeComments: true, collapseWhitespace: true, removeAttributeQuotes: true // more options: // https://github.com/kangax/html-minifier#options-quick-reference }, // necessary to consistently work with multiple chunks via CommonsChunkPlugin chunksSortMode: 'dependency' }), // keep module.id stable when vendor modules does not change new webpack.HashedModuleIdsPlugin(), // enable scope hoisting new webpack.optimize.ModuleConcatenationPlugin(), // split vendor js into its own file new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks (module) { // any required modules inside node_modules are extracted to vendor return ( module.resource && /\.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, '../node_modules') ) === 0 ) } }), // extract webpack runtime and module manifest to its own file in order to // prevent vendor hash from being updated whenever app bundle is updated new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', minChunks: Infinity }), // This instance extracts shared chunks from code splitted chunks and bundles them // in a separate chunk, similar to the vendor chunk // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk new webpack.optimize.CommonsChunkPlugin({ name: 'app', async: 'vendor-async', children: true, minChunks: 3 }), // copy custom static assets new CopyWebpackPlugin([ { from: path.resolve(__dirname, '../static'), to: config.build.assetsSubDirectory, ignore: ['.*'] } ]) ] }) if (config.build.productionGzip) { const CompressionWebpackPlugin = require('compression-webpack-plugin') webpackConfig.plugins.push( new CompressionWebpackPlugin({ asset: '[path].gz[query]', algorithm: 'gzip', test: new RegExp( '\\.(' + config.build.productionGzipExtensions.join('|') + ')$' ), threshold: 10240, minRatio: 0.8 }) ) } if (config.build.bundleAnalyzerReport) { const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin webpackConfig.plugins.push(new BundleAnalyzerPlugin()) } module.exports = webpackConfig ================================================ FILE: ui/config/dev.env.js ================================================ 'use strict' const merge = require('webpack-merge') const prodEnv = require('./prod.env') module.exports = merge(prodEnv, { NODE_ENV: '"development"', BASE_API: '"http://localhost:8000/api"', }) ================================================ FILE: ui/config/index.js ================================================ 'use strict' // Template version: 1.3.1 // see http://vuejs-templates.github.io/webpack for documentation. const path = require('path') module.exports = { dev: { // Paths assetsSubDirectory: 'static', assetsPublicPath: '/', proxyTable: {}, // Various Dev Server settings host: 'localhost', // can be overwritten by process.env.HOST port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined autoOpenBrowser: false, errorOverlay: true, notifyOnErrors: true, poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- // Use Eslint Loader? // If true, your code will be linted during bundling and // linting errors and warnings will be shown in the console. useEslint: true, // If true, eslint errors and warnings will also be shown in the error overlay // in the browser. showEslintErrorsInOverlay: false, /** * Source Maps */ // https://webpack.js.org/configuration/devtool/#development devtool: 'cheap-module-eval-source-map', // If you have problems debugging vue-files in devtools, // set this to false - it *may* help // https://vue-loader.vuejs.org/en/options.html#cachebusting cacheBusting: true, cssSourceMap: true }, build: { // Template for index.html index: path.resolve(__dirname, '../dist/index.html'), // Paths assetsRoot: path.resolve(__dirname, '../dist'), assetsSubDirectory: 'static', assetsPublicPath: '/', /** * Source Maps */ productionSourceMap: true, // https://webpack.js.org/configuration/devtool/#production devtool: '#source-map', // Gzip off by default as many popular static hosts such as // Surge or Netlify already gzip all static assets for you. // Before setting to `true`, make sure to: // npm install --save-dev compression-webpack-plugin productionGzip: false, productionGzipExtensions: ['js', 'css'], // Run the build command with an extra argument to // View the bundle analyzer report after build finishes: // `npm run build --report` // Set to `true` or `false` to always turn it on or off bundleAnalyzerReport: process.env.npm_config_report } } ================================================ FILE: ui/config/prod.env.js ================================================ 'use strict' module.exports = { NODE_ENV: '"production"', BASE_API: '"/api"', } ================================================ FILE: ui/index.html ================================================