Przeglądaj źródła

Merge pull request #1440 from overleaf/as-amd-webpack

Bundle all frontend code with webpack

GitOrigin-RevId: 1bd93dad516c456fe1649193868e841e20459b0b
Alasdair Smith 6 lat temu
rodzic
commit
9cf73f965c
59 zmienionych plików z 839 dodań i 1064 usunięć
  1. 0 1
      services/web/.eslintignore
  2. 2 19
      services/web/.gitignore
  3. 0 1
      services/web/.prettierignore
  4. 0 3
      services/web/.vscode/settings.json
  5. 0 84
      services/web/Gruntfile.js
  6. 4 103
      services/web/Makefile
  7. 0 31
      services/web/Makefile.module
  8. 42 56
      services/web/app/src/infrastructure/ExpressLocals.js
  9. 0 10
      services/web/app/src/infrastructure/Modules.js
  10. 4 1
      services/web/app/src/infrastructure/PackageVersions.js
  11. 4 29
      services/web/app/views/layout.pug
  12. 9 54
      services/web/app/views/project/editor.pug
  13. 1 1
      services/web/app/views/referal/bonus.pug
  14. 0 88
      services/web/app/views/sentry.pug
  15. 1 1
      services/web/app/views/subscriptions/dashboard.pug
  16. 1 1
      services/web/app/views/subscriptions/new.pug
  17. 1 1
      services/web/app/views/subscriptions/plans.pug
  18. 1 1
      services/web/app/views/subscriptions/team/invite.pug
  19. 1 1
      services/web/app/views/user/sessions.pug
  20. 1 1
      services/web/app/views/user/settings/user-oauth.pug
  21. 0 0
      services/web/config/.gitignore
  22. 1 1
      services/web/config/settings.defaults.coffee
  23. 1 1
      services/web/docker-compose.ci.yml
  24. 2 2
      services/web/docker-compose.yml
  25. 7 2
      services/web/install_deps.sh
  26. 14 50
      services/web/karma.conf.js
  27. 1 1
      services/web/modules/launchpad/public/src/main/index.js
  28. 2 4
      services/web/nodemon.frontend.json
  29. 410 342
      services/web/package-lock.json
  30. 9 4
      services/web/package.json
  31. 0 0
      services/web/public/js/libs/algolia-2.5.2.js
  32. 1 1
      services/web/public/js/libs/angular-1.6.4.min.js
  33. 4 0
      services/web/public/js/libs/angular-1.6.4.min.js.map
  34. 0 0
      services/web/public/js/libs/jquery-1.11.1.min.js
  35. 0 0
      services/web/public/js/libs/jquery-1.11.1.min.js.map
  36. 2 24
      services/web/public/js/libs/sharejs.js
  37. 2 2
      services/web/public/src/analytics/AbTestingManager.js
  38. 2 2
      services/web/public/src/ide.js
  39. 2 2
      services/web/public/src/ide/chat/services/chatMessages.js
  40. 2 2
      services/web/public/src/ide/colors/ColorManager.js
  41. 3 3
      services/web/public/src/ide/editor/directives/aceEditor.js
  42. 1 1
      services/web/public/src/ide/online-users/OnlineUsersManager.js
  43. 1 1
      services/web/public/src/ide/pdfng/directives/pdfAnnotations.js
  44. 1 1
      services/web/public/src/ide/pdfng/directives/pdfHighlights.js
  45. 1 1
      services/web/public/src/ide/pdfng/directives/pdfRenderer.js
  46. 63 57
      services/web/public/src/ide/pdfng/directives/pdfViewer.js
  47. 1 1
      services/web/public/src/ide/review-panel/filters/notEmpty.js
  48. 1 1
      services/web/public/src/ide/review-panel/filters/numKeys.js
  49. 1 1
      services/web/public/src/main.js
  50. 2 2
      services/web/scripts/example/script_for_migration.js
  51. 16 0
      services/web/test/unit_frontend/bootstrap.js
  52. 22 0
      services/web/test/unit_frontend/import_tests.js
  53. 0 8
      services/web/test/unit_frontend/src/mocks/ide/file-tree/util/fileOperationI18nNames.js
  54. 0 0
      services/web/test/unit_frontend/src/support/fixture.js
  55. 0 34
      services/web/test/unit_frontend/src/test-main.js
  56. 11 10
      services/web/webpack.config.dev.js
  57. 124 13
      services/web/webpack.config.js
  58. 46 3
      services/web/webpack.config.prod.js
  59. 11 0
      services/web/webpack.config.test.js

+ 0 - 1
services/web/.eslintignore

@@ -3,4 +3,3 @@ modules/**/scripts
 public/js
 public/minjs
 modules/**/public/js
-test/unit_frontend/js

+ 2 - 19
services/web/.gitignore

@@ -42,31 +42,14 @@ TpdsWorker.js
 BackgroundJobsWorker.js
 UserAndProjectPopulator.coffee
 
-public/es/modules
-
-public/js/*.js
-public/js/*.map
-public/js/analytics/
-public/js/directives/
-public/js/components/
-public/js/es/
-public/js/filters/
-public/js/ide/
-public/js/main/
-public/js/modules/
-public/js/services/
-public/js/utils/
+public/js/manifest.json
+public/minjs/
 
 public/stylesheets/style*.css
 public/stylesheets/sl-style*.css
 public/stylesheets/light-style*.css
 public/stylesheets/ieee-style*.css
 public/stylesheets/*.map
-public/minjs/
-
-public/js/libs/require*.js
-
-test/unit_frontend/js/
 
 Gemfile.lock
 

+ 0 - 1
services/web/.prettierignore

@@ -3,4 +3,3 @@ modules/**/scripts
 public/js
 public/minjs
 modules/**/public/js
-test/unit_frontend/js

+ 0 - 3
services/web/.vscode/settings.json

@@ -1,8 +1,5 @@
 {
   "files.exclude": {
-    "app/js": true,
-    "public/js": true,
-    "test/unit_frontend/js": true,
     "node_modules": true,
     "data": true
   }

+ 0 - 84
services/web/Gruntfile.js

@@ -1,84 +0,0 @@
-/* eslint-disable
-    max-len,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-const fs = require('fs')
-const PackageVersions = require('./app/src/infrastructure/PackageVersions')
-const Settings = require('settings-sharelatex')
-require('es6-promise').polyfill()
-
-module.exports = function(grunt) {
-  grunt.loadNpmTasks('grunt-contrib-requirejs')
-  grunt.loadNpmTasks('grunt-file-append')
-
-  const config = {
-    requirejs: {
-      compile: {
-        options: {
-          optimize: 'uglify2',
-          appDir: 'public/js',
-          baseUrl: './',
-          dir: 'public/minjs',
-          inlineText: false,
-          generateSourceMaps: true,
-          preserveLicenseComments: false,
-          paths: {
-            moment: `libs/${PackageVersions.lib('moment')}`,
-            mathjax: '/js/libs/mathjax/MathJax.js?config=TeX-AMS_HTML',
-            'pdfjs-dist/build/pdf': `libs/${PackageVersions.lib('pdfjs')}/pdf`,
-            ace: `${PackageVersions.lib('ace')}`,
-            fineuploader: `libs/${PackageVersions.lib('fineuploader')}`,
-            recurly: 'https://js.recurly.com/v4/recurly'
-          },
-
-          skipDirOptimize: true,
-          modules: [
-            {
-              name: 'main',
-              exclude: ['libraries']
-            },
-            {
-              name: 'ide',
-              exclude: ['pdfjs-dist/build/pdf', 'libraries']
-            },
-            {
-              name: 'libraries'
-            },
-            {
-              name: 'ace/mode-latex'
-            },
-            {
-              name: 'ace/worker-latex'
-            }
-          ]
-        }
-      }
-    },
-
-    file_append: {
-      default_options: {
-        files: [
-          {
-            append: '\n//ide.js is complete - used for automated testing',
-            input: 'public/minjs/ide.js',
-            output: 'public/minjs/ide.js'
-          }
-        ]
-      }
-    }
-  }
-
-  grunt.initConfig(config)
-  return grunt.registerTask(
-    'compile:minify',
-    'Concat and minify the client side js',
-    ['requirejs', 'file_append']
-  )
-}

+ 4 - 103
services/web/Makefile

@@ -1,6 +1,5 @@
 DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml --log-level ERROR
 
-
 BUILD_NUMBER ?= local
 BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
 PROJECT_NAME = web
@@ -16,16 +15,9 @@ MODULE_DIRS := $(shell find modules -mindepth 1 -maxdepth 1 -type d -not -name '
 MODULE_MAKEFILES := $(MODULE_DIRS:=/Makefile)
 MODULE_NAME=$(shell basename $(MODULE))
 
-BABEL := node_modules/.bin/babel
-GRUNT := node_modules/.bin/grunt
 LESSC := node_modules/.bin/lessc
 CLEANCSS := node_modules/.bin/cleancss
 
-SRC_FILES := $(shell find public/src -name '*.js')
-DIST_FILES := $(subst src,js,$(SRC_FILES))
-MAIN_SRC_FILES := $(shell find modules -type f -wholename '*main/index.js')
-IDE_SRC_FILES := $(shell find modules -type f -wholename '*ide/index.js')
-
 LESS_FILES := $(shell find public/stylesheets -name '*.less')
 LESSC_COMMON_FLAGS := --source-map --autoprefix="last 2 versions, ie >= 10"
 CLEANCSS_FLAGS := --s0 --source-map
@@ -41,45 +33,6 @@ CSS_OL_IEEE_FILE := public/stylesheets/ieee-style.css
 
 CSS_FILES := $(CSS_SL_FILE) $(CSS_OL_FILE) $(CSS_OL_LIGHT_FILE) $(CSS_OL_IEEE_FILE)
 
-# The automatic variable $(@D) is the target directory name
-public/js/%.js: public/src/%.js
-	@mkdir -p $(@D)
-	$(BABEL) $< --out-file $@
-
-test/unit_frontend/js/%.js: test/unit_frontend/src/%.js
-	@mkdir -p $(@D)
-	$(BABEL) $< --out-file $@
-
-public/js/ide.js: public/src/ide.js $(IDE_SRC_FILES)
-	@echo Compiling and injecting module includes into public/js/ide.js
-	@INCLUDES=""; \
-	for dir in modules/*; \
-	do \
-		MODULE=`echo $$dir | cut -d/ -f2`; \
-		if [ -e $$dir/public/src/ide/index.js ]; then \
-			INCLUDES="\"ide/$$MODULE/index\",$$INCLUDES"; \
-		fi \
-	done; \
-	INCLUDES=$${INCLUDES%?}; \
-	$(BABEL) $< | \
-		sed -e s=\'__IDE_CLIENTSIDE_INCLUDES__\'=$$INCLUDES= \
-		> $@
-
-public/js/main.js: public/src/main.js $(MAIN_SRC_FILES)
-	@echo Compiling and injecting module includes into public/js/main.js
-	@INCLUDES=""; \
-	for dir in modules/*; \
-	do \
-		MODULE=`echo $$dir | cut -d/ -f2`; \
-		if [ -e $$dir/public/src/main/index.js ]; then \
-			INCLUDES="\"main/$$MODULE/index\",$$INCLUDES"; \
-		fi \
-	done; \
-	INCLUDES=$${INCLUDES%?}; \
-	$(BABEL) $< | \
-		sed -e s=\'__MAIN_CLIENTSIDE_INCLUDES__\'=$$INCLUDES= \
-		> $@
-
 public/stylesheets/%.css: $(LESS_FILES)
 	$(LESSC) $(LESSC_COMMON_FLAGS) $(@D)/$*.less $(@D)/$*.css
 
@@ -87,58 +40,15 @@ css_full: $(CSS_FILES)
 
 css: $(CSS_OL_FILE)
 
-minify: $(CSS_FILES) $(DIST_FILES)
-	$(GRUNT) compile:minify
-	$(MAKE) minify_css
-	$(MAKE) minify_es
-
-minify_css: $(CSS_FILES) 
+minify: $(CSS_FILES)
 	$(CLEANCSS) $(CLEANCSS_FLAGS) -o $(CSS_SL_FILE) $(CSS_SL_FILE)
 	$(CLEANCSS) $(CLEANCSS_FLAGS) -o $(CSS_OL_FILE) $(CSS_OL_FILE)
 	$(CLEANCSS) $(CLEANCSS_FLAGS) -o $(CSS_OL_LIGHT_FILE) $(CSS_OL_LIGHT_FILE)
 	$(CLEANCSS) $(CLEANCSS_FLAGS) -o $(CSS_OL_IEEE_FILE) $(CSS_OL_IEEE_FILE)
 
-minify_es:
-	npm -q run webpack:production
-
-compile: $(DIST_FILES) css public/js/main.js public/js/ide.js
-	@$(MAKE) compile_modules
+compile: css
 
-compile_full:
-	$(BABEL) public/src --out-dir public/js
-	$(BABEL) test/unit_frontend/src --out-dir test/unit_frontend/js
-	rm -f public/js/ide.js public/js/main.js # We need to generate ide.js, main.js, manually later
-	$(MAKE) css_full
-	$(MAKE) compile_modules_full
-	$(MAKE) compile # ide.js, main.js, share.js, and anything missed
-
-compile_css_full:
-	$(MAKE) css_full
-
-compile_modules: $(MODULE_MAKEFILES)
-	@set -e; \
-	for dir in $(MODULE_DIRS); \
-	do \
-		if [ -e $$dir/Makefile ]; then \
-			(cd $$dir && $(MAKE) compile); \
-		fi; \
-		if [ ! -e $$dir/Makefile ]; then \
-			echo "No makefile found in $$dir"; \
-		fi; \
-	done
-
-compile_modules_full: $(MODULE_MAKEFILES)
-	@set -e; \
-	for dir in $(MODULE_DIRS); \
-	do \
-		if [ -e $$dir/Makefile ]; then \
-			echo "Compiling $$dir in full"; \
-			(cd $$dir && $(MAKE) compile_full); \
-		fi; \
-		if [ ! -e $$dir/Makefile ]; then \
-			echo "No makefile found in $$dir"; \
-		fi; \
-	done
+compile_full: css_full
 
 $(MODULE_MAKEFILES): Makefile.module
 	@set -e; \
@@ -147,18 +57,9 @@ $(MODULE_MAKEFILES): Makefile.module
 		cp Makefile.module $$makefile; \
 	done
 
-clean: clean_frontend clean_css clean_tests
-
-clean_frontend:
-	rm -rf public/js/{analytics,directives,es,filters,ide,main,modules,services,utils}
-	rm -f public/js/*.{js,map}
-
-clean_css:
+clean:
 	rm -f public/stylesheets/*.css*
 
-clean_tests:
-	rm -rf test/unit_frontend/js
-
 clean_ci:
 	$(DOCKER_COMPOSE) down -v -t 0
 	docker container list | grep 'days ago' | cut -d ' ' -f 1 - | xargs -r docker container stop

+ 0 - 31
services/web/Makefile.module

@@ -1,16 +1,6 @@
 MODULE_NAME := $(notdir $(shell pwd))
 MODULE_DIR := modules/$(MODULE_NAME)
 PROJECT_NAME = web
-BABEL := ../../node_modules/.bin/babel
-
-IDE_SRC_FILES := $(shell [ -e public/src/ide ] && find public/src/ide -name '*.js')
-IDE_DIST_FILES := $(subst public/src/ide,../../public/js/ide/$(MODULE_NAME),$(IDE_SRC_FILES))
-
-IDE_TEST_SRC_FILES := $(shell [ -e test/unit_frontend/src/ide ] && find test/unit_frontend/src/ide -name '*.js')
-IDE_TEST_DIST_FILES := $(subst test/unit_frontend/src/ide,../../test/unit_frontend/js/ide/$(MODULE_NAME),$(IDE_TEST_SRC_FILES))
-
-MAIN_SRC_FILES := $(shell [ -e public/src/main ] && find public/src/main -name '*.js')
-MAIN_DIST_FILES := $(subst public/src/main,../../public/js/main/$(MODULE_NAME),$(MAIN_SRC_FILES))
 
 DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
 DOCKER_COMPOSE_MODULE_FLAGS := ${DOCKER_COMPOSE_FLAGS} -f $(MODULE_DIR)/docker-compose.yml
@@ -22,26 +12,5 @@ DOCKER_COMPOSE := cd ../../ && \
 	MOCHA_GREP=${MOCHA_GREP} \
 	docker-compose ${DOCKER_COMPOSE_MODULE_FLAGS}
 
-../../test/unit_frontend/js/ide/$(MODULE_NAME)/%.js: test/unit_frontend/src/ide/%.js
-	@mkdir -p $(dir $@)
-	$(BABEL) $< --out-file $@
-
-../../public/js/ide/$(MODULE_NAME)/%.js: public/src/ide/%.js
-	@mkdir -p $(dir $@)
-	$(BABEL) $< --out-file $@
-
-../../public/js/main/$(MODULE_NAME)/%.js: public/src/main/%.js
-	@mkdir -p $(dir $@)
-	$(BABEL) $< --out-file $@
-
-compile: $(IDE_DIST_FILES) $(MAIN_DIST_FILES) $(IDE_TEST_DIST_FILES)
-	@echo > /dev/null
-
-compile_full:
-	if [ -e public/src/ide ]; then $(BABEL) public/src/ide --out-dir ../../public/js/ide/$(MODULE_NAME); fi
-	if [ -e public/src/main ]; then $(BABEL) public/src/main --out-dir ../../public/js/main/$(MODULE_NAME); fi
-	if [ -e test/unit_frontend/src/ide ]; then $(BABEL) test/unit_frontend/src/ide --out-dir ../../test/unit_frontend/js/ide/$(MODULE_NAME); fi
-	@$(MAKE) compile # Anything else missed
-
 test_acceptance:
 	${DOCKER_COMPOSE} run --rm test_acceptance npm -q run test:acceptance:run_dir -- ${MOCHA_ARGS} $(MODULE_DIR)/test/acceptance/src

+ 42 - 56
services/web/app/src/infrastructure/ExpressLocals.js

@@ -18,7 +18,6 @@
  * DS207: Consider shorter variations of null checks
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-let path
 const logger = require('logger-sharelatex')
 const fs = require('fs')
 const crypto = require('crypto')
@@ -39,12 +38,34 @@ const Features = require('./Features')
 Modules = require('./Modules')
 const moment = require('moment')
 const lodash = require('lodash')
+const chokidar = require('chokidar')
 
 const jsPath = Settings.useMinifiedJs ? '/minjs/' : '/js/'
 
-const ace = PackageVersions.lib('ace')
-const pdfjs = PackageVersions.lib('pdfjs')
-const fineuploader = PackageVersions.lib('fineuploader')
+const webpackManifestPath = Path.join(
+  __dirname,
+  `../../../public${jsPath}manifest.json`
+)
+let webpackManifest
+if (['development', 'test'].includes(process.env.NODE_ENV)) {
+  // In dev the web and webpack containers can race (and therefore the manifest
+  // file may not be created when web is running), so watch the file for changes
+  // and reload
+  webpackManifest = {}
+  const reloadManifest = () => {
+    logger.log('[DEV] Reloading webpack manifest')
+    webpackManifest = require(webpackManifestPath)
+  }
+
+  logger.log('[DEV] Watching webpack manifest')
+  chokidar
+    .watch(webpackManifestPath)
+    .on('add', reloadManifest)
+    .on('change', reloadManifest)
+} else {
+  logger.log('[PRODUCTION] Loading webpack manifest')
+  webpackManifest = require(webpackManifestPath)
+}
 
 const getFileContent = function(filePath) {
   filePath = Path.join(__dirname, '../../../', `public${filePath}`)
@@ -59,21 +80,17 @@ const getFileContent = function(filePath) {
 }
 
 const pathList = [
-  `${jsPath}libs/require.js`,
-  `${jsPath}ide.js`,
-  `${jsPath}main.js`,
-  `${jsPath}libraries.js`,
   '/stylesheets/style.css',
   '/stylesheets/light-style.css',
   '/stylesheets/ieee-style.css',
   '/stylesheets/sl-style.css'
-].concat(Modules.moduleAssetFiles(jsPath))
+]
 
 if (!Settings.useMinifiedJs) {
   logger.log('not using minified JS, not hashing static files')
 } else {
   logger.log('Generating file hashes...')
-  for (path of Array.from(pathList)) {
+  for (let path of Array.from(pathList)) {
     const content = getFileContent(path)
     const hash = crypto
       .createHash('md5')
@@ -171,56 +188,34 @@ module.exports = function(app, webRouter, privateApiRouter, publicApiRouter) {
       staticFilesBase = ''
     }
 
-    res.locals.jsPath = jsPath
-    res.locals.fullJsPath = Url.resolve(staticFilesBase, jsPath)
     res.locals.lib = PackageVersions.lib
 
     res.locals.moment = moment
 
-    res.locals.buildJsPath = function(jsFile, opts) {
-      if (opts == null) {
-        opts = {}
-      }
-      path = Path.join(jsPath, jsFile)
-
-      if (opts.hashedPath && hashedFiles[path] != null) {
-        path = hashedFiles[path]
-      }
-
-      if (opts.qs == null) {
-        opts.qs = {}
+    res.locals.buildJsPath = function(jsFile, opts = {}) {
+      // Resolve path from webpack manifest file
+      let path = webpackManifest[jsFile]
+      // If not found in manifest, it is directly linked, so fallback to
+      // relevant public directory
+      if (!path) {
+        path = Path.join(jsPath, jsFile)
       }
 
       if (opts.cdn !== false) {
         path = Url.resolve(staticFilesBase, path)
       }
 
-      const qs = querystring.stringify(opts.qs)
-
-      if (opts.removeExtension === true) {
-        path = path.slice(0, -3)
+      if (opts.qs) {
+        path = path + '?' + querystring.stringify(opts.qs)
       }
 
-      if (qs != null && qs.length > 0) {
-        path = path + '?' + qs
-      }
       return path
     }
 
-    res.locals.buildWebpackPath = function(jsFile, opts) {
-      if (opts == null) {
-        opts = {}
-      }
-      if (Settings.webpack != null && !Settings.useMinifiedJs) {
-        path = Path.join(jsPath, jsFile)
-        if (opts.removeExtension === true) {
-          path = path.slice(0, -3)
-        }
-        return `${Settings.webpack.url}/public${path}`
-      } else {
-        return res.locals.buildJsPath(jsFile, opts)
-      }
-    }
+    res.locals.mathJaxPath = res.locals.buildJsPath('libs/mathjax/MathJax.js', {
+      cdn: false,
+      qs: { config: 'TeX-AMS_HTML,Safe' }
+    })
 
     const IEEE_BRAND_ID = 15
     res.locals.isIEEE = brandVariation =>
@@ -249,7 +244,7 @@ module.exports = function(app, webRouter, privateApiRouter, publicApiRouter) {
 
     res.locals.buildCssPath = function(themeModifier, buildOpts) {
       const cssFileName = _buildCssFileName(themeModifier)
-      path = Path.join('/stylesheets/', cssFileName)
+      const path = Path.join('/stylesheets/', cssFileName)
       if (
         (buildOpts != null ? buildOpts.hashedPath : undefined) &&
         hashedFiles[path] != null
@@ -261,15 +256,10 @@ module.exports = function(app, webRouter, privateApiRouter, publicApiRouter) {
     }
 
     res.locals.buildImgPath = function(imgFile) {
-      path = Path.join('/img/', imgFile)
+      const path = Path.join('/img/', imgFile)
       return Url.resolve(staticFilesBase, path)
     }
 
-    res.locals.mathJaxPath = res.locals.buildJsPath('libs/mathjax/MathJax.js', {
-      cdn: false,
-      qs: { config: 'TeX-AMS_HTML,Safe' }
-    })
-
     return next()
   })
 
@@ -421,10 +411,6 @@ module.exports = function(app, webRouter, privateApiRouter, publicApiRouter) {
     }
     res.locals.gaToken = Settings.analytics && Settings.analytics.ga.token
     res.locals.tenderUrl = Settings.tenderUrl
-    res.locals.sentrySrc =
-      Settings.sentry != null ? Settings.sentry.src : undefined
-    res.locals.sentryPublicDSN =
-      Settings.sentry != null ? Settings.sentry.publicDSN : undefined
     return next()
   })
 

+ 0 - 10
services/web/app/src/infrastructure/Modules.js

@@ -114,16 +114,6 @@ module.exports = Modules = {
     return (Modules.viewIncludes[view] || []).length > 0
   },
 
-  moduleAssetFiles(pathPrefix) {
-    const assetFiles = []
-    for (let module of Array.from(this.modules)) {
-      for (let assetFile of Array.from(module.assetFiles || [])) {
-        assetFiles.push(`${pathPrefix}${assetFile}`)
-      }
-    }
-    return assetFiles
-  },
-
   linkedFileAgentsIncludes() {
     const agents = {}
     for (let module of Array.from(this.modules)) {

+ 4 - 1
services/web/app/src/infrastructure/PackageVersions.js

@@ -9,7 +9,10 @@ const version = {
   pdfjs: '2.0.943',
   moment: '2.9.0',
   ace: '1.4.5', // Upgrade instructions: https://github.com/overleaf/write_latex/wiki/Upgrading-Ace
-  fineuploader: '5.15.4'
+  fineuploader: '5.15.4',
+  // For frontend. Backend has a different version defined in package.json
+  underscore: '1.9.1',
+  algolia: '2.5.2'
 }
 
 module.exports = {

+ 4 - 29
services/web/app/views/layout.pug

@@ -53,7 +53,7 @@ html(
 				window.location.search += '&'+noCdnKey;
 			}
 
-		block scripts
+		block head-scripts
 
 		script(src=buildJsPath("libs/angular-1.6.4.min.js"))
 
@@ -61,7 +61,6 @@ html(
 			window.sharelatex = {
 				siteUrl: '#{settings.siteUrl}',
 				wsUrl: '#{settings.wsUrl}',
-				jsPath:  '#{jsPath}',
 			};
 			window.systemMessages = !{StringHelper.stringifyJsonForScript(systemMessages)};
 			window.ab = {};
@@ -116,33 +115,9 @@ html(
 			span(ng-controller="ScribtexPopupController")
 			include scribtex-modal
 
-		block requirejs
-			block requirejs-config
-				script(type='text/javascript').
-					// minimal requirejs configuration (can be extended/overridden)
-					window.requirejs = {
-						"paths" : {
-							"moment": "libs/#{lib('moment')}",
-							"fineuploader": "libs/#{lib('fineuploader')}",
-							"main": "#{buildJsPath('main.js', {hashedPath:settings.useMinifiedJs, removeExtension:true})}",
-							"libraries": "#{buildJsPath('libraries.js', {hashedPath:settings.useMinifiedJs, removeExtension:true})}"
-						},
-						"config":{
-							"moment":{
-								"noGlobal": true
-							}
-						}
-					};
-
-			block requirejs-init
-				script(
-					data-main=buildJsPath('main.js', {hashedPath:false}),
-					baseurl=fullJsPath,
-					src=buildJsPath('libs/require.js', {hashedPath:true})
-				)
-
 		!= moduleIncludes("contactModal", locals)
 		include v1-tooltip
-		include sentry
-
 
+		block foot-scripts
+			script(src=buildJsPath('libraries.js'))
+			script(src=buildJsPath('main.js'))

+ 9 - 54
services/web/app/views/project/editor.pug

@@ -113,16 +113,6 @@ block content
 			h3 {{ title }}
 		.modal-body(ng-bind-html="message")
 
-	script(src=mathJaxPath)
-
-block requirejs
-	script(type="text/javascript" src='/socket.io/socket.io.js')
-
-	//- don't use cdn for workers
-	- var aceWorkerPath = buildJsPath(lib('ace'), {cdn:false})
-	- var pdfWorkerPath = buildJsPath('/libs/' + lib('pdfjs') + '/pdf.worker', {cdn:false})
-	- var pdfCMapsPath = buildJsPath('/libs/' + lib('pdfjs') + '/bcmaps/', {cdn:false})
-
 	//- We need to do .replace(/\//g, '\\/') do that '</script>' -> '<\/script>'
 	//- and doesn't prematurely end the script tag.
 	script#data(type="application/json").
@@ -146,44 +136,10 @@ block requirejs
 		window.trackChangesState = data.trackChangesState;
 		window.wikiEnabled = #{!!(settings.apis.wiki && settings.apis.wiki.url)};
 		window.gitBridgePublicBaseUrl = '#{gitBridgePublicBaseUrl}'
-		window.requirejs = {
-			"paths" : {
-				"moment": "libs/#{lib('moment')}",
-				"pdfjs-dist/build/pdf": "libs/#{lib('pdfjs')}/pdf",
-				"pdfjs-dist/build/pdf.worker": "#{pdfWorkerPath}",
-				"ace": "#{lib('ace')}",
-				"fineuploader": "libs/#{lib('fineuploader')}",
-				"ide": "#{buildJsPath('ide.js', {hashedPath:settings.useMinifiedJs, removeExtension:true})}",
-				"libraries": "#{buildJsPath('libraries.js', {hashedPath:settings.useMinifiedJs, removeExtension:true})}",
-				!{moduleIncludes("editor:script", locals)}
-				!{moduleIncludes("publish:script", locals)}
-			},
-			"waitSeconds": 0,
-			"shim": {
-				"ace/ext-searchbox": {
-					"deps": ["ace/ace"]
-				},
-				"ace/ext-modelist": {
-					"deps": ["ace/ace"]
-				},
-				"ace/ext-language_tools": {
-					"deps": ["ace/ace"]
-				},
-				"ace/keybinding-vim": {
-					"deps": ["ace/ace"]
-				},
-				"libs/jquery.ui.touch-punch": {
-					"deps": [ "libs/#{lib('jquery-layout')}" ]
-				}
-			},
-			"config":{
-				"moment":{
-					"noGlobal": true
-				}
-			}
-		};
-		window.aceWorkerPath = "#{aceWorkerPath}";
-		window.pdfCMapsPath = "#{pdfCMapsPath}"
+		//- Set base path for Ace scripts loaded on demand/workers and don't use cdn
+		window.aceBasePath = "#{buildJsPath(lib('ace'), {cdn:false})}"
+		//- Set path for PDFjs CMaps
+		window.pdfCMapsPath = "#{buildJsPath('cmaps/')}"
 		window.uiConfig = JSON.parse('!{StringHelper.stringifyJsonForScript(uiConfig)}');
 		//- enable doc hash checking for all projects
 		//- used in public/js/libs/sharejs.js
@@ -193,9 +149,8 @@ block requirejs
 		script(type='text/javascript').
 			window.overallThemes = JSON.parse('!{StringHelper.stringifyJsonForScript(overallThemes)}');
 
-	script(
-		data-main=buildJsPath("ide.js", {hashedPath:false}),
-		baseurl=fullJsPath,
-		data-ace-base=buildJsPath(lib('ace')),
-		src=buildJsPath('libs/require.js', {hashedPath:true})
-	)
+block foot-scripts
+	script(type="text/javascript" src='/socket.io/socket.io.js')
+	script(src=mathJaxPath)
+	script(src=buildJsPath('libraries.js'))
+	script(src=buildJsPath('ide.js'))

+ 1 - 1
services/web/app/views/referal/bonus.pug

@@ -1,6 +1,6 @@
 extends ../layout
 
-block scripts
+block head-scripts
 	script(type='text/javascript').
 		$(document).ready(function () {
 			$.ajax({dataType: "script", cache: true, url: "//connect.facebook.net/en_US/all.js"}).done(function () {

+ 0 - 88
services/web/app/views/sentry.pug

@@ -1,88 +0,0 @@
-- if (typeof(sentryPublicDSN) != "undefined")
-	script(type="text/javascript").
-		require.config({
-			paths: {
-				'raven': 'libs/raven-3.27.0.min'
-			}
-		});
-
-		require(["raven"], function(Raven) {
-			if (typeof(Raven) != "undefined" && Raven.config) {
-				Raven.config("#{sentryPublicDSN}", {
-				tags: { 'commit': '@@COMMIT@@', 'build': '@@RELEASE@@' },
-				release: '@@RELEASE@@',
-				// Ignore list based off: https://gist.github.com/1878283
-				ignoreErrors: [
-					'DealPly',
-					// Random plugins/extensions
-					'top.GLOBALS',
-					// See: http://blog.errorception.com/2012/03/tale-of-unfindable-js-error.html
-					'originalCreateNotification',
-					'canvas.contentDocument',
-					'MyApp_RemoveAllHighlights',
-					'http://tt.epicplay.com',
-					'Can\'t find variable: ZiteReader',
-					'jigsaw is not defined',
-					'ComboSearch is not defined',
-					'http://loading.retry.widdit.com/',
-					'atomicFindClose',
-					// Facebook borked
-					'fb_xd_fragment',
-					// ISP optimizing proxy - `Cache-Control: no-transform` seems to reduce this. (thanks @acdha)
-					// See http://stackoverflow.com/questions/4113268/how-to-stop-javascript-injection-from-vodafone-proxy
-					'bmi_SafeAddOnload',
-					'EBCallBackMessageReceived',
-					// See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx
-					'conduitPage',
-					'/NS_ERROR_NOT_CONNECTED/i',
-					"/Cannot read property 'row' of undefined/i",
-					'TypeError: start is undefined'
-				],
-				ignoreUrls: [
-					// Facebook flakiness
-					/graph\.facebook\.com/i,
-					// Facebook blocked
-					/connect\.facebook\.net\/en_US\/all\.js/i,
-					// Woopra flakiness
-					/eatdifferent\.com\.woopra-ns\.com/i,
-					/static\.woopra\.com\/js\/woopra\.js/i,
-					// Chrome extensions
-					/extensions\//i,
-					/^chrome:\/\//i,
-					// Other plugins
-					/127\.0\.0\.1:4001\/isrunning/i,  // Cacaoweb
-					/webappstoolbarba\.texthelp\.com\//i,
-					/metrics\.itunes\.apple\.com\.edgesuite\.net\//i,
-					/a\.disquscdn\.com/i,
-					/platform\.twitter\.com/i,
-					/pstatic\.datafastguru\.info/i
-				],
-				shouldSendCallback: function(data) {
-					// only send a fraction of errors
-					var sampleRate = 0.01;
-					return (Math.random() <= sampleRate);
-				},
-				dataCallback: function(data) {
-					// remove circular references from object
-					var cache = [];
-					var s = JSON.stringify(data, function(k, v) { if (typeof v === 'object' && v !== null) { if (cache.indexOf(v) !== -1) return "[circular]"; cache.push(v); }; return v; });
-					return JSON.parse(s);
-				}
-				// we highly recommend restricting exceptions to a domain in order to filter out clutter
-				// whitelistUrls: ['example.com/scripts/']
-				}).install();
-				//- We conditionally depend on Raven (based on whether the sentry
-				//- config is set or not). This does not play well with require.js when
-				//- minifying as it is expecting all dependencies to be defined at
-				//- compile time. Previously Raven added itself as a global, so we just
-				//- mimic that old behaviour
-				window.Raven = Raven
-			}
-		}, function(err) {
-			console.log(">> error loading raven", err);
-		})
-	- if (user && typeof(user) != "undefined" && typeof (user.email) != "undefined")
-		script(type="text/javascript").
-			if (typeof(Raven) != "undefined" && Raven.setUserContext) {
-				Raven.setUserContext({email: '#{user.email}'});
-			}

+ 1 - 1
services/web/app/views/subscriptions/dashboard.pug

@@ -2,7 +2,7 @@ extends ../layout
 
 include ./dashboard/_team_name_mixin
 
-block scripts
+block head-scripts
 	script(src="https://js.recurly.com/v4/recurly.js")
 
 block content

+ 1 - 1
services/web/app/views/subscriptions/new.pug

@@ -1,6 +1,6 @@
 extends ../layout
 
-block scripts
+block head-scripts
 	script(src="https://js.recurly.com/v4/recurly.js")
 	script(type='text/javascript').
 		window.countryCode = '#{countryCode}'

+ 1 - 1
services/web/app/views/subscriptions/plans.pug

@@ -6,7 +6,7 @@ include _plans_page_tables
 block vars
 	- metadata = { viewport: true }
 
-block scripts
+block head-scripts
 	script(type='text/javascript').
 		window.recomendedCurrency = '#{recomendedCurrency}';
 		window.abCurrencyFlag = '#{abCurrencyFlag}';

+ 1 - 1
services/web/app/views/subscriptions/team/invite.pug

@@ -1,6 +1,6 @@
 extends ../../layout
 
-block scripts
+block head-scripts
 	script(type='text/javascript').
 		window.teamId = '#{teamId}'
 		window.hasIndividualRecurlySubscription = #{hasIndividualRecurlySubscription}

+ 1 - 1
services/web/app/views/user/sessions.pug

@@ -1,7 +1,7 @@
 extends ../layout
 
 
-block scripts
+block head-scripts
 	script(type='text/javascript').
 		window.otherSessions = !{StringHelper.stringifyJsonForScript(sessions)}
 

+ 1 - 1
services/web/app/views/user/settings/user-oauth.pug

@@ -1,4 +1,4 @@
-block scripts
+block head-scripts
 	script(type='text/javascript').
 		window.oauthProviders = !{StringHelper.stringifyJsonForScript(oauthProviders)}
 		window.thirdPartyIds = !{StringHelper.stringifyJsonForScript(thirdPartyIds)}

+ 0 - 0
services/web/config/.gitignore


+ 1 - 1
services/web/config/settings.defaults.coffee

@@ -506,7 +506,7 @@ module.exports = settings =
 	redirects:
 		"/templates/index": "/templates/"
 
-	reloadModuleViewsOnEachRequest: process.env['NODE_ENV'] != 'production'
+	reloadModuleViewsOnEachRequest: ['development', 'test'].includes(process.env['NODE_ENV'])
 
 	domainLicences: [
 

+ 1 - 1
services/web/docker-compose.ci.yml

@@ -23,7 +23,7 @@ services:
       ENABLED_LINKED_FILE_TYPES: 'url,project_file,project_output_file,mendeley,zotero'
       LINKED_URL_PROXY: 'http://localhost:6543'
       SHARELATEX_CONFIG: /app/test/acceptance/config/settings.test.coffee
-      NODE_ENV: production
+      NODE_ENV: test
     user: root
     depends_on:
       - redis

+ 2 - 2
services/web/docker-compose.yml

@@ -29,7 +29,7 @@ services:
       ENABLED_LINKED_FILE_TYPES: 'url,project_file,project_output_file,mendeley,zotero'
       SHARELATEX_CONFIG: /app/test/acceptance/config/settings.test.coffee
       MOCHA_GREP: ${MOCHA_GREP}
-      NODE_ENV: production
+      NODE_ENV: test
     depends_on:
       - redis
       - mongo
@@ -42,7 +42,7 @@ services:
     volumes:
       - .:/app
     working_dir: /app
-    command: npm run test:frontend -- --single-run
+    command: npm run test:frontend:single
 
   redis:
     image: redis

+ 7 - 2
services/web/install_deps.sh

@@ -3,14 +3,19 @@
 make --no-print-directory format & FORMAT=$!
 make --no-print-directory lint & LINT=$!
 npm install git+https://github.com/sharelatex/translations-sharelatex.git#master & TRANSLATIONS=$!
-WEBPACK_ENV=production make minify & MINIFY=$!
+
+# CSS
+make minify & MINIFY=$!
+# JS
+npm run webpack:production & WEBPACK=$!
 
 echo "Waiting for lint, format, translations and minify to finish"
 
 wait $LINT && echo "Lint complete" || exit 1
 wait $FORMAT && echo "Format complete" || exit 1
 wait $TRANSLATIONS && echo "Translations install complete" || exit 1
-wait $MINIFY && echo "Minifiy complete" || exit 1
+wait $MINIFY && echo "Minify complete" || exit 1
+wait $WEBPACK && echo "Webpack complete" || exit 1
 
 chmod -R 0755 /app/public
 chown -R node:node /app/public

+ 14 - 50
services/web/karma.conf.js

@@ -1,4 +1,4 @@
-const path = require('path')
+const webpackConfig = require('./webpack.config.test')
 
 module.exports = function(config) {
   config.set({
@@ -13,64 +13,29 @@ module.exports = function(config) {
     },
     browsers: ['ChromeCustom'],
     files: [
-      'test/unit_frontend/es/es-bootstrap.js',
-      // Angular must be loaded before requirejs to set up angular global
+      'test/unit_frontend/bootstrap.js',
+      // Include scripts that are injected into the page outside of webpack
       'public/js/libs/angular-1.6.4.min.js',
-      'public/js/libs/angular-mocks.js',
       'public/js/libs/jquery-1.11.1.min.js',
-      'public/js/libs/underscore-1.3.3.js',
-      // Set up requirejs
-      'test/unit_frontend/js/test-main.js',
-      // Include source & test files, but don't "include" them as requirejs
-      // handles this for us
-      { pattern: 'public/js/**/*.js', included: false },
-      { pattern: 'test/unit_frontend/js/**/*.js', included: false },
-      // Include ES test files
-      'test/unit_frontend/es/**/*.js',
-      'modules/**/test/unit_frontend/es/**/*.js',
+
+      // Allow mocking of angular
+      'public/js/libs/angular-mocks.js',
+
+      // Import all tests (see comment in the file for why this is necessary)
+      'test/unit_frontend/import_tests.js',
+
       // Include CSS (there is some in js/libs dir)
       'public/stylesheets/**/*.css',
       'public/js/libs/**/*.css'
     ],
     middleware: ['fake-img'],
     preprocessors: {
-      // Run ES test files through webpack (which will then include source
-      // files in bundle)
-      'test/unit_frontend/es/**/*.js': ['webpack'],
-      'modules/**/test/unit_frontend/es/**/*.js': ['webpack']
+      // Run files through webpack
+      'test/unit_frontend/import_tests.js': ['webpack']
     },
-    frameworks: ['requirejs', 'mocha', 'chai-sinon'],
+    frameworks: ['mocha', 'chai-sinon'],
     // Configure webpack in the tests
-    webpack: {
-      module: {
-        rules: [
-          {
-            // Pass application JS files through babel-loader, compiling to ES5
-            test: /\.js$/,
-            // Only compile application files (dependencies are in ES5 already)
-            exclude: /node_modules/,
-            use: [
-              {
-                loader: 'babel-loader',
-                options: {
-                  // Configure babel-loader to cache compiled output so that
-                  // subsequent compile runs are much faster
-                  cacheDirectory: true
-                }
-              }
-            ]
-          }
-        ]
-      },
-      resolve: {
-        // Alias common directories in import pathnames to cut down on the
-        // amount of ../../ etc
-        alias: {
-          Src: path.join(__dirname, 'public/es/'),
-          Modules: path.join(__dirname, 'modules')
-        }
-      }
-    },
+    webpack: webpackConfig,
     // Configure the webpack dev server used to serve test files
     webpackMiddleware: {
       // Disable noisy CLI output
@@ -78,7 +43,6 @@ module.exports = function(config) {
     },
     plugins: [
       require('karma-chrome-launcher'),
-      require('karma-requirejs'),
       require('karma-mocha'),
       require('karma-chai-sinon'),
       require('karma-webpack'),

+ 1 - 1
services/web/modules/launchpad/public/src/main/index.js

@@ -1,3 +1,3 @@
 // TODO: This file was created by bulk-decaffeinate.
 // Sanity-check the conversion and remove this comment.
-define(['main/launchpad/controllers/LaunchpadController'], function() {})
+define(['./controllers/LaunchpadController'], function() {})

+ 2 - 4
services/web/nodemon.frontend.json

@@ -1,9 +1,7 @@
 {
   "exec": "make compile || exit 1",
   "watch": [
-    "public/src/",
-    "public/stylesheets/",
-    "modules/**/public/src/"
+    "public/stylesheets/"
   ],
-  "ext": "js less"
+  "ext": "less"
 }

Plik diff jest za duży
+ 410 - 342
services/web/package-lock.json


+ 9 - 4
services/web/package.json

@@ -17,6 +17,7 @@
     "test:unit:ci": "bin/unit_test --timeout 10000",
     "test:unit:app": "bin/unit_test_app $@",
     "test:frontend": "karma start",
+    "test:frontend:single": "karma start --single-run",
     "compile": "make compile",
     "start": "npm -q run compile && node $NODE_APP_OPTIONS app.js",
     "nodemon": "nodemon --config nodemon.json",
@@ -48,6 +49,7 @@
     "contentful": "^6.1.1",
     "cookie": "^0.2.3",
     "cookie-parser": "1.3.5",
+    "crypto-js": "^3.1.9-1",
     "csurf": "^1.8.3",
     "d3": "^3.5.16",
     "dateformat": "1.0.4-1.2.3",
@@ -98,6 +100,7 @@
     "passport-orcid": "0.0.3",
     "passport-saml": "^1.1.0",
     "passport-twitter": "^1.0.4",
+    "pdfjs-dist": "^2.0.943",
     "pug": "^2.0.0-beta6",
     "react": "^15.4.2",
     "react-dom": "^15.4.2",
@@ -129,7 +132,10 @@
     "chai-as-promised": "^7.1.1",
     "chaid": "^1.0.2",
     "cheerio": "^1.0.0-rc.3",
+    "chokidar": "^3.1.1",
     "clean-css-cli": "^4.2.1",
+    "copy-webpack-plugin": "^5.0.4",
+    "dom-testing-library": "^3.19.4",
     "es6-promise": "^4.0.5",
     "eslint": "^4.18.1",
     "eslint-config-prettier": "^3.1.0",
@@ -145,11 +151,8 @@
     "eslint-plugin-promise": "^3.6.0",
     "eslint-plugin-react": "^7.11.1",
     "eslint-plugin-standard": "^3.0.1",
+    "expose-loader": "^0.7.5",
     "glob": "^7.1.3",
-    "grunt": "0.4.5",
-    "grunt-cli": "^1.2.0",
-    "grunt-contrib-requirejs": "0.4.1",
-    "grunt-file-append": "0.0.6",
     "handlebars-loader": "^1.7.0",
     "karma": "^2.0.0",
     "karma-chai-sinon": "^0.1.5",
@@ -172,9 +175,11 @@
     "sinon-mongoose": "^2.3.0",
     "timekeeper": "^2.2.0",
     "translations-sharelatex": "git+https://github.com/sharelatex/translations-sharelatex.git#master",
+    "val-loader": "^1.1.1",
     "webpack": "^4.39.0",
     "webpack-cli": "^3.3.6",
     "webpack-dev-server": "^3.7.2",
+    "webpack-manifest-plugin": "^2.0.4",
     "webpack-merge": "^4.2.1"
   }
 }

Plik diff jest za duży
+ 0 - 0
services/web/public/js/libs/algolia-2.5.2.js


+ 1 - 1
services/web/public/js/libs/angular-1.6.4.min.js

@@ -329,4 +329,4 @@ b.length<=e}}}}},$c=function(){return{restrict:"A",require:"?ngModel",link:funct
 "PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),STANDALONEMONTH:"January February March April May June July August September October November December".split(" "),WEEKENDRANGE:[5,
 6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,maxFrac:2,minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",localeID:"en_US",pluralCat:function(a,
 c){var e=a|0,f=c;void 0===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),B(function(){ue(x.document,Sc)}))})(window);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>');
-//# sourceMappingURL=angular.min.js.map
+//# sourceMappingURL=angular-1.6.4.min.js.map

Plik diff jest za duży
+ 4 - 0
services/web/public/js/libs/angular-1.6.4.min.js.map


Plik diff jest za duży
+ 0 - 0
services/web/public/js/libs/jquery-1.11.1.min.js


Plik diff jest za duży
+ 0 - 0
services/web/public/js/libs/jquery-1.11.1.min.js.map


+ 2 - 24
services/web/public/js/libs/sharejs.js

@@ -26,7 +26,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
  * DS207: Consider shorter variations of null checks
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-define(['ace/ace','libs/sha1'], function () {
+define(['ace/ace','crypto-js/sha1'], function (_ignore, CryptoJSSHA1) {
   var append = void 0,
       bootstrapTransform = void 0,
       exports = void 0,
@@ -226,9 +226,6 @@ define(['ace/ace','libs/sha1'], function () {
     };
   };
 
-  if (typeof WEB === 'undefined') {
-    exports.bootstrapTransform = bootstrapTransform;
-  }
   // A simple text implementation
   //
   // Operations are lists of components.
@@ -673,21 +670,10 @@ define(['ace/ace','libs/sha1'], function () {
 
     // [] is used to prevent closure from renaming types.text
     exports.types.text = text;
-  } else {
-    module.exports = text;
-
-    // The text type really shouldn't need this - it should be possible to define
-    // an efficient transform function by making a sort of transform map and passing each
-    // op component through it.
-    require('./helpers').bootstrapTransform(text, transformComponent, checkValidOp, append);
   }
 
   // Text document API for text
 
-  if (typeof WEB === 'undefined') {
-    text = require('./text');
-  }
-
   text.api = {
     provides: { text: true },
 
@@ -878,10 +864,6 @@ define(['ace/ace','libs/sha1'], function () {
     module.exports = MicroEvent;
   }
 
-  if (WEB == null) {
-    types = require('../types');
-  }
-
   if (WEB != null) {
     exports.extendDoc = function (name, fn) {
       return Doc.prototype[name] = fn;
@@ -1311,7 +1293,7 @@ define(['ace/ace','libs/sha1'], function () {
           var needToRecomputeHash = !this.__lastSubmitTimestamp || (age > RECOMPUTE_HASH_INTERVAL) || (age < 0) 
           if (needToRecomputeHash || window.sl_debugging) {
             // send git hash of current snapshot
-            var sha1 = CryptoJS.SHA1("blob " + this.snapshot.length + "\x00" + this.snapshot).toString()
+            var sha1 = CryptoJSSHA1("blob " + this.snapshot.length + "\x00" + this.snapshot).toString()
             this.__lastSubmitTimestamp = now;
           }
         }
@@ -1435,10 +1417,6 @@ define(['ace/ace','libs/sha1'], function () {
   // Make documents event emitters
 
 
-  if (WEB == null) {
-    MicroEvent = require('./microevent');
-  }
-
   MicroEvent.mixin(Doc);
 
   exports.Doc = Doc;

+ 2 - 2
services/web/public/src/analytics/AbTestingManager.js

@@ -13,7 +13,7 @@
  * DS207: Consider shorter variations of null checks
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-define(['base', 'libs/md5'], function(App) {
+define(['base', 'crypto-js/md5'], function(App, CryptoJS) {
   const oldKeys = [
     'sl_abt_multi_currency_editor_eu-eu',
     'sl_abt_multi_currency_eu-eu',
@@ -73,7 +73,7 @@ define(['base', 'libs/md5'], function(App) {
         user_uuid = Math.random()
         ipCookie(sl_user_test_token, user_uuid, { expires: 365, path: '/' })
       }
-      const hash = CryptoJS.MD5(`${user_uuid}:${testName}`)
+      const hash = CryptoJS(`${user_uuid}:${testName}`)
       return hash
     }
 

+ 2 - 2
services/web/public/src/ide.js

@@ -43,7 +43,6 @@ define([
   'ide/directives/layout',
   'ide/directives/validFile',
   'ide/services/ide',
-  '__IDE_CLIENTSIDE_INCLUDES__',
   'analytics/AbTestingManager',
   'directives/focus',
   'directives/fineUpload',
@@ -60,7 +59,8 @@ define([
   'filters/formatDate',
   'main/event',
   'main/account-upgrade',
-  'main/exposed-settings'
+  'main/exposed-settings',
+  '../../modules/modules-ide.js'
 ], function(
   App,
   FileTreeManager,

+ 2 - 2
services/web/public/src/ide/chat/services/chatMessages.js

@@ -14,7 +14,7 @@
  * DS207: Consider shorter variations of null checks
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-define(['base', 'libs/md5'], App =>
+define(['base', 'crypto-js/md5'], (App, CryptoJS) =>
   App.factory('chatMessages', function($http, ide) {
     const MESSAGES_URL = `/project/${ide.project_id}/messages`
     const MESSAGE_LIMIT = 50
@@ -152,7 +152,7 @@ define(['base', 'libs/md5'], App =>
     }
 
     var formatUser = function(user) {
-      const hash = CryptoJS.MD5(user.email.toLowerCase())
+      const hash = CryptoJS(user.email.toLowerCase())
       user.gravatar_url = `//www.gravatar.com/avatar/${hash}`
       return user
     }

+ 2 - 2
services/web/public/src/ide/colors/ColorManager.js

@@ -14,7 +14,7 @@
  * DS207: Consider shorter variations of null checks
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-define([], function() {
+define(['crypto-js/md5'], function(CryptoJS) {
   let ColorManager
   return (ColorManager = {
     getColorScheme(hue, element) {
@@ -72,7 +72,7 @@ define([], function() {
     },
 
     getHueForId(id) {
-      const hash = CryptoJS.MD5(id)
+      const hash = CryptoJS(id)
       let hue = parseInt(hash.toString().slice(0, 8), 16) % 320
       return hue
     }

+ 3 - 3
services/web/public/src/ide/editor/directives/aceEditor.js

@@ -45,10 +45,10 @@ define([
   const { Vim } = ace.require('ace/keyboard/vim')
   const SearchBox = ace.require('ace/ext/searchbox')
 
-  // set the path for ace workers if using a CDN (from editor.pug)
-  if (window.aceWorkerPath !== '') {
+  // Set the base path that ace will fetch modes/snippets/workers from
+  if (window.aceBasePath !== '') {
     syntaxValidationEnabled = true
-    ace.config.set('workerPath', `${window.aceWorkerPath}`)
+    ace.config.set('basePath', `${window.aceBasePath}`)
   } else {
     syntaxValidationEnabled = false
   }

+ 1 - 1
services/web/public/src/ide/online-users/OnlineUsersManager.js

@@ -17,7 +17,7 @@
  */
 define([
   'ide/colors/ColorManager',
-  'libs/md5',
+  'crypto-js/md5',
   'ide/online-users/controllers/OnlineUsersController'
 ], function(ColorManager) {
   let OnlineUsersManager

+ 1 - 1
services/web/public/src/ide/pdfng/directives/pdfAnnotations.js

@@ -1,4 +1,4 @@
-define(['base', 'pdfjs-dist/build/pdf'], (App, PDFJS) => {
+define(['base', 'pdfjs-dist/webpack'], (App, PDFJS) => {
   const EXTERNAL_LINK_TARGET = '_blank'
   const REL_NOOPENER = 'noreferrer noopener'
 

+ 1 - 1
services/web/public/src/ide/pdfng/directives/pdfHighlights.js

@@ -13,7 +13,7 @@
  * DS207: Consider shorter variations of null checks
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-define(['base', 'pdfjs-dist/build/pdf'], (App, PDFJS) =>
+define(['base', 'pdfjs-dist/webpack'], (App, PDFJS) =>
   // app = angular.module 'pdfHighlights', []
 
   App.factory('pdfHighlights', function() {

+ 1 - 1
services/web/public/src/ide/pdfng/directives/pdfRenderer.js

@@ -18,7 +18,7 @@
  * DS207: Consider shorter variations of null checks
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-define(['base', 'pdfjs-dist/build/pdf'], (App, PDFJS) =>
+define(['base', 'pdfjs-dist/webpack'], (App, PDFJS) =>
   // App = angular.module 'PDFRenderer', ['pdfAnnotations', 'pdfTextLayer']
 
   App.factory('PDFRenderer', function(

+ 63 - 57
services/web/public/src/ide/pdfng/directives/pdfViewer.js

@@ -50,64 +50,70 @@ define([
     this.load = function() {
       // $scope.pages = []
 
-      if ($scope.document != null) {
-        $scope.document.destroy()
-      }
-      $scope.loadCount = $scope.loadCount != null ? $scope.loadCount + 1 : 1
-      // TODO need a proper url manipulation library to add to query string
-      let url = $scope.pdfSrc
-      // add 'pdfng=true' to show that we are using the angular pdfjs viewer
-      const queryStringExists = /\?/.test(url)
-      url = url + (!queryStringExists ? '?' : '&') + 'pdfng=true'
-      // for isolated compiles, load the pdf on-demand because nobody will overwrite it
-      const onDemandLoading = true
-      $scope.document = new PDFRenderer(url, {
-        scale: 1,
-        disableAutoFetch: onDemandLoading ? true : undefined,
-        navigateFn(ref) {
-          // this function captures clicks on the annotation links
-          $scope.navigateTo = ref
-          return $scope.$apply()
-        },
-        progressCallback(progress) {
-          return $scope.$emit('progress', progress)
-        },
-        loadedCallback() {
-          return $scope.$emit('loaded')
-        },
-        errorCallback(error) {
-          __guardMethod__(window.Raven, 'captureMessage', o =>
-            o.captureMessage(`pdfng error ${error}`)
-          )
-          return $scope.$emit('pdf:error', error)
-        },
-        pageSizeChangeCallback(pageNum, deltaH) {
-          return $scope.$broadcast('pdf:page:size-change', pageNum, deltaH)
-        }
-      })
-
-      // we will have all the main information needed to start display
-      // after the following promise is resolved
-      return ($scope.loaded = $q
-        .all({
-          numPages: $scope.document.getNumPages(),
-          // get size of first page as default @ scale 1
-          pdfViewport: $scope.document.getPdfViewport(1, 1)
-        })
-        .then(function(result) {
-          $scope.pdfViewport = result.pdfViewport
-          $scope.pdfPageSize = [
-            result.pdfViewport.height,
-            result.pdfViewport.width
-          ]
-          // console.log 'resolved q.all, page size is', result
-          $scope.$emit('loaded')
-          return ($scope.numPages = result.numPages)
+      // Ensure previous document is torn down before loading the next one (to
+      // prevent race conditions)
+      const documentTearDown =
+        $scope.document != null ? $scope.document.destroy() : Promise.resolve()
+
+      return documentTearDown.then(() => {
+        $scope.loadCount = $scope.loadCount != null ? $scope.loadCount + 1 : 1
+        // TODO need a proper url manipulation library to add to query string
+        let url = $scope.pdfSrc
+        // add 'pdfng=true' to show that we are using the angular pdfjs viewer
+        const queryStringExists = /\?/.test(url)
+        url = url + (!queryStringExists ? '?' : '&') + 'pdfng=true'
+        // for isolated compiles, load the pdf on-demand because nobody will overwrite it
+        const onDemandLoading = true
+        $scope.document = new PDFRenderer(url, {
+          scale: 1,
+          disableAutoFetch: onDemandLoading ? true : undefined,
+          navigateFn(ref) {
+            // this function captures clicks on the annotation links
+            $scope.navigateTo = ref
+            return $scope.$apply()
+          },
+          progressCallback(progress) {
+            return $scope.$emit('progress', progress)
+          },
+          loadedCallback() {
+            return $scope.$emit('loaded')
+          },
+          errorCallback(error) {
+            __guardMethod__(window.Raven, 'captureMessage', o =>
+              o.captureMessage(`pdfng error ${error}`)
+            )
+            return $scope.$emit('pdf:error', error)
+          },
+          pageSizeChangeCallback(pageNum, deltaH) {
+            return $scope.$broadcast('pdf:page:size-change', pageNum, deltaH)
+          }
         })
-        .catch(function(error) {
-          $scope.$emit('pdf:error', error)
-          return $q.reject(error)
-        }))
+
+        // we will have all the main information needed to start display
+        // after the following promise is resolved
+        $scope.loaded = $q
+          .all({
+            numPages: $scope.document.getNumPages(),
+            // get size of first page as default @ scale 1
+            pdfViewport: $scope.document.getPdfViewport(1, 1)
+          })
+          .then(function(result) {
+            $scope.pdfViewport = result.pdfViewport
+            $scope.pdfPageSize = [
+              result.pdfViewport.height,
+              result.pdfViewport.width
+            ]
+            // console.log 'resolved q.all, page size is', result
+            $scope.$emit('loaded')
+            return ($scope.numPages = result.numPages)
+          })
+          .catch(function(error) {
+            $scope.$emit('pdf:error', error)
+            return $q.reject(error)
+          })
+
+        return $scope.loaded
+      })
     }
 
     this.setScale = (scale, containerHeight, containerWidth) =>

+ 1 - 1
services/web/public/src/ide/review-panel/filters/notEmpty.js

@@ -9,4 +9,4 @@
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
 define(['base'], App =>
-  app.filter('notEmpty', () => object => !angular.equals({}, object)))
+  App.filter('notEmpty', () => object => !angular.equals({}, object)))

+ 1 - 1
services/web/public/src/ide/review-panel/filters/numKeys.js

@@ -10,7 +10,7 @@
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
 define(['base'], App =>
-  app.filter(
+  App.filter(
     'numKeys',
     () =>
       function(object) {

+ 1 - 1
services/web/public/src/main.js

@@ -56,7 +56,7 @@ define([
   'services/validateCaptchaV3',
   'filters/formatDate',
   'components/inputSuggestions',
-  '__MAIN_CLIENTSIDE_INCLUDES__'
+  '../../modules/modules-main.js'
 ], function() {
   angular.module('SharelatexApp').config(function($locationProvider) {
     try {

+ 2 - 2
services/web/scripts/example/script_for_migration.js

@@ -7,12 +7,12 @@
  */
 
 const { User } = require('../../app/src/models/User')
-require('colors')
+// const somePackage = require('some-package')
 
 const runScript = async () => {
   const user = await User.findOne({}, { first_name: 1 }).exec()
   const name = user ? user.first_name : 'World'
-  console.log(`Hello ${name}!`.rainbow.underline.bold)
+  console.log(`Hello ${name}!`)
 }
 
 if (!module.parent) {

+ 16 - 0
services/web/test/unit_frontend/es/es-bootstrap.js → services/web/test/unit_frontend/bootstrap.js

@@ -17,3 +17,19 @@ chai.Assertion.addMethod('equalPos', function(expectedPos) {
     `Pos({ line: ${actualLine}, ch: ${actualCh} })`
   )
 })
+
+// Mock ExposedSettings
+window.ExposedSettings = {}
+
+// Mock the file operation I18n names that are stored in the DOM
+function mockFileOperationI18nNames(id, text) {
+  const el = document.createElement('div')
+  el.id = id
+  el.innerText = text
+  el.setAttribute('hidden', true)
+  document.body.appendChild(el)
+}
+mockFileOperationI18nNames('file_action_edited_str', 'edited')
+mockFileOperationI18nNames('file_action_renamed_str', 'renamed')
+mockFileOperationI18nNames('file_action_created_str', 'created')
+mockFileOperationI18nNames('file_action_deleted_str', 'deleted')

+ 22 - 0
services/web/test/unit_frontend/import_tests.js

@@ -0,0 +1,22 @@
+/*
+ * Bundle all test files together into a single bundle, and run tests against
+ * this single bundle.
+ * We are using karma-webpack to bundle our tests and the 'default' strategy is
+ * to create a bundle for each test file. This isolates the tests better, but
+ * causes a problem with Angular. The issue with Angular tests is because we
+ * load a single global copy of Angular (see karma.conf.js) but
+ * public/src/base.js is included in each bundle, meaning the Angular app is
+ * initialised for each bundle when it is loaded onto the page when Karma
+ * starts. This means that only the last bundle will have controllers/directives
+ * registered against it, ultimately meaning that all other bundles will fail
+ * because Angular cannot find the controller/directive under test.
+ */
+
+// Import from the top-level any JS files within a test/unit_frontend/src
+// directory
+const context = require.context(
+  '../../',
+  true,
+  /test\/unit_frontend\/src\/.*\.js$/
+)
+context.keys().forEach(context)

+ 0 - 8
services/web/test/unit_frontend/src/mocks/ide/file-tree/util/fileOperationI18nNames.js

@@ -1,8 +0,0 @@
-define([], () => {
-  return {
-    edited: 'edited',
-    renamed: 'renamed',
-    created: 'created',
-    deleted: 'deleted'
-  }
-})

+ 0 - 0
services/web/test/unit_frontend/es/support/fixture.js → services/web/test/unit_frontend/src/support/fixture.js


+ 0 - 34
services/web/test/unit_frontend/src/test-main.js

@@ -1,34 +0,0 @@
-/* eslint-disable
-    no-undef,
-    max-len,
-*/
-// Set up requirejs to load the tests and mocked dependencies
-// For tests, uses heuristic that test filenames end with Tests.js (existing
-// frontend code) or _tests.js (newer frontend code)
-// For mocks, uses heuristic that loads any .js file within the mocks subfolder
-const testDeps = []
-for (let file in window.__karma__.files) {
-  if (window.__karma__.files.hasOwnProperty(file)) {
-    if (
-      /test\/unit_frontend\/js.+(_t|T)ests\.js$/.test(file) ||
-      /test\/unit_frontend\/js\/mocks\/.+\.js$/.test(file)
-    ) {
-      testDeps.push(file)
-    }
-  }
-}
-
-requirejs.config({
-  baseUrl: '/base/public/js',
-  paths: {
-    moment: 'libs/moment-2.9.0'
-  },
-  map: {
-    '*': {
-      'ide/file-tree/util/fileOperationI18nNames':
-        '../../test/unit_frontend/js/mocks/ide/file-tree/util/fileOperationI18nNames'
-    }
-  },
-  deps: testDeps,
-  callback: window.__karma__.start
-})

+ 11 - 10
services/web/webpack.config.dev.js

@@ -1,3 +1,4 @@
+const path = require('path')
 const merge = require('webpack-merge')
 
 const base = require('./webpack.config')
@@ -9,7 +10,7 @@ module.exports = merge(base, {
   devtool: 'cheap-module-eval-source-map',
 
   output: {
-    publicPath: '/public/js/es/'
+    publicPath: '/js/'
   },
 
   devServer: {
@@ -27,21 +28,21 @@ module.exports = merge(base, {
       'Access-Control-Allow-Origin': '*'
     },
 
+    // Serve all content from public via webpack. This allows for serving assets
+    // not (currently) bundled with webpack to be served as normal scripts
+    contentBase: path.join(__dirname, 'public'),
+
     // Customise output to the (node) console
     stats: {
       colors: true, // Enable some coloured highlighting
-      timings: true, // Show build timing info
-      assets: true, // Show output bundles
-      warnings: true, // Show build warnings
       // Hide some overly verbose output
+      performance: false, // Disable as code is uncompressed in dev mode
       hash: false,
       version: false,
-      chunks: false
+      chunks: false,
+      modules: false,
+      // Hide cmaps from asset output
+      excludeAssets: [/cmap/]
     }
-  },
-
-  // Disable performance budget warnings as code is uncompressed in dev mode
-  performance: {
-    hints: false
   }
 })

+ 124 - 13
services/web/webpack.config.js

@@ -1,13 +1,22 @@
 const fs = require('fs')
 const path = require('path')
+const webpack = require('webpack')
+const CopyPlugin = require('copy-webpack-plugin')
+const ManifestPlugin = require('webpack-manifest-plugin')
+
+const PackageVersions = require('./app/src/infrastructure/PackageVersions')
 
 const MODULES_PATH = path.join(__dirname, '/modules')
 
 // Generate a hash of entry points, including modules
-const entryPoints = {}
+const entryPoints = {
+  main: './public/src/main.js',
+  ide: './public/src/ide.js'
+}
+
 if (fs.existsSync(MODULES_PATH)) {
   fs.readdirSync(MODULES_PATH).reduce((acc, module) => {
-    const entryPath = path.join(MODULES_PATH, module, '/public/es/index.js')
+    const entryPath = path.join(MODULES_PATH, module, '/public/src/index.js')
     if (fs.existsSync(entryPath)) {
       acc[module] = entryPath
     }
@@ -15,12 +24,6 @@ if (fs.existsSync(MODULES_PATH)) {
   }, entryPoints)
 }
 
-// If no entry points are found, silently exit
-if (!Object.keys(entryPoints).length) {
-  console.warn('No entry points found, exiting')
-  process.exit(0)
-}
-
 module.exports = {
   // Defines the "entry point(s)" for the application - i.e. the file which
   // bootstraps the application
@@ -30,7 +33,7 @@ module.exports = {
   // Note: webpack-dev-server does not write the bundle to disk, instead it is
   // kept in memory for speed
   output: {
-    path: path.join(__dirname, '/public/js/es'),
+    path: path.join(__dirname, '/public/js'),
 
     filename: '[name].js',
 
@@ -61,7 +64,7 @@ module.exports = {
         ]
       },
       {
-        // These options are neccesary for handlebars to have access to helper
+        // These options are necessary for handlebars to have access to helper
         // methods
         test: /\.handlebars$/,
         loader: 'handlebars-loader',
@@ -70,14 +73,122 @@ module.exports = {
           knownHelpersOnly: false,
           runtimePath: 'handlebars/runtime'
         }
+      },
+      // Allow for injection of modules dependencies by reading contents of
+      // modules directory and adding necessary dependencies
+      {
+        test: path.join(__dirname, 'modules/modules-main.js'),
+        use: [
+          {
+            loader: 'val-loader'
+          }
+        ]
+      },
+      {
+        test: path.join(__dirname, 'modules/modules-ide.js'),
+        use: [
+          {
+            loader: 'val-loader'
+          }
+        ]
+      },
+      {
+        // Expose underscore global variable
+        test: path.join(
+          __dirname,
+          `public/js/libs/${PackageVersions.lib('underscore')}.js`
+        ),
+        use: [
+          {
+            loader: 'expose-loader',
+            options: '_'
+          }
+        ]
+      },
+      {
+        // Expose Algolia global variable
+        test: path.join(
+          __dirname,
+          `public/js/libs/${PackageVersions.lib('algolia')}.js`
+        ),
+        use: [
+          {
+            loader: 'expose-loader',
+            options: 'AlgoliaSearch'
+          }
+        ]
       }
     ]
   },
   resolve: {
     alias: {
-      // makes handlebars globally accessible to backbone
-      handlebars: 'handlebars/dist/handlebars.min.js',
-      jquery: path.join(__dirname, 'node_modules/jquery/dist/jquery')
+      // Aliases for AMD modules
+
+      // Vendored dependencies in public/js/libs (e.g. angular)
+      libs: path.join(__dirname, 'public/js/libs'),
+      // Use vendored moment (with correct version)
+      moment: path.join(
+        __dirname,
+        `public/js/libs/${PackageVersions.lib('moment')}`
+      ),
+      // Enables ace/ace shortcut
+      ace: path.join(__dirname, `public/js/${PackageVersions.lib('ace')}`),
+      // fineupload vendored dependency (which we're aliasing to fineuploadER
+      // for some reason)
+      fineuploader: path.join(
+        __dirname,
+        `public/js/libs/${PackageVersions.lib('fineuploader')}`
+      )
+    },
+    // Define what can be imported with out an absolute or relative path. This
+    // is because we need to override the default (which is just node_modules)
+    // to get AMD modules in public/src to work as they do not use relative/
+    // absolute paths for dependencies
+    modules: [path.join(__dirname, 'public/src'), 'node_modules']
+  },
+
+  // Split out vendored dependencies that are shared between 2 or more "real
+  // bundles" (e.g. ide.js/main.js) as a separate "libraries" bundle and ensure
+  // that they are de-duplicated from the other bundles. This allows the
+  // libraries bundle to be independently cached (as it likely will change less
+  // than the other bundles)
+  optimization: {
+    splitChunks: {
+      cacheGroups: {
+        libraries: {
+          test: /[\\/]node_modules[\\/]|[\\/]public[\\/]js[\\/]libs[\\/]/,
+          name: 'libraries',
+          chunks: 'initial',
+          minChunks: 2
+        }
+      }
     }
+  },
+
+  plugins: [
+    // Generate a manifest.json file which is used by the backend to map the
+    // base filenames to the generated output filenames
+    new ManifestPlugin({
+      // Always write the manifest file to disk (even if in dev mode, where
+      // files are held in memory). This is needed because the server will read
+      // this file (from disk) when building the script's url
+      writeToFileEmit: true
+    }),
+
+    // Silence warning when loading moment from vendored dependencies as it
+    // attempts to load locales.js file which does not exist (but this is fine
+    // as we don't want to load the large amount of locale data from moment)
+    new webpack.IgnorePlugin(/^\.\/locale$/, /public\/js\/libs/),
+
+    // Copy CMap files from pdfjs-dist package to build output. These are used
+    // to provide support for non-Latin characters
+    new CopyPlugin([{ from: 'node_modules/pdfjs-dist/cmaps', to: 'cmaps' }])
+  ],
+
+  // If jquery or underscore is required by another dependency *don't* include
+  // in the bundle and use the relevant global variable instead
+  externals: {
+    jquery: '$',
+    underscore: '_'
   }
 }

+ 46 - 3
services/web/webpack.config.prod.js

@@ -1,15 +1,58 @@
 const path = require('path')
 const merge = require('webpack-merge')
+const CopyPlugin = require('copy-webpack-plugin')
 
 const base = require('./webpack.config')
+const PackageVersions = require('./app/src/infrastructure/PackageVersions')
 
 module.exports = merge(base, {
   mode: 'production',
 
-  devtool: false,
+  // Enable a full source map. Generates a comment linking to the source map
+  devtool: 'source-map',
 
   output: {
     // Override output path to minjs dir
-    path: path.join(__dirname, '/public/minjs/es')
-  }
+    path: path.join(__dirname, '/public/minjs'),
+
+    // Override filename to include hash for immutable caching
+    filename: '[name]-[chunkhash].js',
+
+    publicPath: '/minjs/'
+  },
+
+  plugins: [
+    // Copy vendored dependencies to minjs directory as these are directly
+    // loaded in a script tag by instead of being part of the webpack build
+    new CopyPlugin([
+      {
+        from: 'public/js/libs/angular-1.6.4.min.js',
+        to: 'libs/angular-1.6.4.min.js'
+      },
+      {
+        from: 'public/js/libs/angular-1.6.4.min.js.map',
+        to: 'libs/angular-1.6.4.min.js.map'
+      },
+      {
+        from: 'public/js/libs/jquery-1.11.1.min.js',
+        to: 'libs/jquery-1.11.1.min.js'
+      },
+      {
+        from: 'public/js/libs/jquery-1.11.1.min.js.map',
+        to: 'libs/jquery-1.11.1.min.js.map'
+      },
+      {
+        from: 'public/js/libs/mathjax',
+        to: 'libs/mathjax'
+      },
+      {
+        from: 'public/js/libs/sigma-master',
+        to: 'libs/sigma-master'
+      },
+      {
+        from: `public/js/ace-${PackageVersions.version.ace}/`,
+        to: `ace-${PackageVersions.version.ace}/`
+      }
+    ])
+  ]
 })

+ 11 - 0
services/web/webpack.config.test.js

@@ -0,0 +1,11 @@
+const merge = require('webpack-merge')
+
+const base = require('./webpack.config')
+
+module.exports = merge(base, {
+  mode: 'development',
+
+  // Karma configures entry & output for us, so disable these
+  entry: null,
+  output: null
+})

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików