diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 1b16b94b6..43c05fffa 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -27,6 +27,9 @@ jobs: - script: 'yarn install' displayName: 'Install Dependencies' + - script: 'yarn build' + displayName: 'Build' + - script: 'test -d dist' displayName: 'Check Build' diff --git a/README.md b/README.md index 6a80b0b09..4ee78ed78 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ Jellyfin Web is the frontend used for most of the clients available for end user ### Dependencies - Yarn +- Gulp-cli ### Getting Started @@ -62,3 +63,12 @@ Jellyfin Web is the frontend used for most of the clients available for end user ```sh yarn serve ``` + +4. Build the client with sourcemaps. + '''sh + yarn + ''' + Or without sourcemaps + '''sh + yarn --production + ''' \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 000000000..04a8028f5 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,123 @@ +'use strict'; + +const { src, dest, series, parallel, watch } = require('gulp'); +const browserSync = require('browser-sync').create(); +const del = require('del'); +const babel = require('gulp-babel'); +const concat = require('gulp-concat'); +const terser = require('gulp-terser'); +const htmlmin = require('gulp-htmlmin'); +const imagemin = require('gulp-imagemin'); +const sourcemaps = require('gulp-sourcemaps'); +const mode = require('gulp-mode')({ + modes: ["production", "development"], + default: "development", + verbose: false +}); +const webpack_stream = require('webpack-stream'); +const inject = require('gulp-inject'); +const postcss = require('gulp-postcss'); +const sass = require('gulp-sass'); + +sass.compiler = require('node-sass') + + +if (mode.production()) { + var webpack_config = require('./webpack.prod.js'); +} else { + var webpack_config = require('./webpack.dev.js'); +} + +function serve() { + browserSync.init({ + server: { + baseDir: "./dist" + }, + port: 8080 + }); + + watch(['src/**/*.js', '!src/bundle.js'], javascript); + watch('src/bundle.js', webpack); + watch('src/**/*.css', css); + watch(['src/**/*.html', '!src/index.html'], html); + watch(['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg'], images); + watch(['src/**/*.json', 'src/**/*.ico'], copy); + watch('src/index.html', injectBundle); + watch(['src/standalone.js', 'src/scripts/apploader.js'], setStandalone); +} + +function setStandalone() { + return src(['src/standalone.js', 'src/scripts/apploader.js'], {base: './src/'}) + .pipe(concat('scripts/apploader.js')) + .pipe(dest('dist/')); +} + +// Clean assets +function clean() { + return del(['dist/']); +} + +function javascript() { + return src(['src/**/*.js', '!src/bundle.js'], {base: './src/'}) + .pipe(mode.development(sourcemaps.init({loadMaps: true}))) + .pipe(babel({ + presets: [ + ['@babel/preset-env'] + ] + })) + .pipe(terser({ + keep_fnames: true, + mangle: false + })) + .pipe(mode.development(sourcemaps.write('.'))) + .pipe(dest('dist/')) + .pipe(browserSync.stream()); +} + +function webpack() { + return webpack_stream(webpack_config) + .pipe(dest('dist/')) + .pipe(browserSync.stream()); +} + +function css() { + return src(['src/**/*.css', 'src/**/*.scss'], {base: './src/'}) + .pipe(mode.development(sourcemaps.init({loadMaps: true}))) + .pipe(sass().on('error', sass.logError)) + .pipe(postcss()) + .pipe(mode.development(sourcemaps.write('.'))) + .pipe(dest('dist/')) + .pipe(browserSync.stream()); +} + +function html() { + return src(['src/**/*.html', '!src/index.html'], {base: './src/'}) + .pipe(mode.production(htmlmin({ collapseWhitespace: true }))) + .pipe(dest('dist/')) + .pipe(browserSync.stream()); +} + +function images() { + return src(['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg'], {base: './src/'}) + .pipe(imagemin()) + .pipe(dest('dist/')) + .pipe(browserSync.stream()); +} + +function copy() { + return src(['src/**/*.json', 'src/**/*.ico'], {base: './src/'}) + .pipe(dest('dist/')) + .pipe(browserSync.stream()); +} + +function injectBundle() { + return src('src/index.html', {base: './src/'}) + .pipe(inject( + src(['src/scripts/apploader.js'], {read: false}, {base: './src/'}), {relative: true} + )) + .pipe(dest('dist/')) + .pipe(browserSync.stream()); +} + +exports.default = series(clean, parallel(javascript, webpack, css, html, images, copy), injectBundle) +exports.serve = series(exports.default, setStandalone, serve) diff --git a/package.json b/package.json index 9fcf535a4..126985798 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,35 @@ "repository": "https://github.com/jellyfin/jellyfin-web", "license": "GPL-2.0-or-later", "devDependencies": { + "@babel/core": "^7.8.6", + "@babel/polyfill": "^7.8.7", + "@babel/preset-env": "^7.8.6", + "autoprefixer": "^9.7.4", + "babel-loader": "^8.0.6", + "browser-sync": "^2.26.7", "clean-webpack-plugin": "^3.0.0", "copy-webpack-plugin": "^5.1.1", "css-loader": "^3.4.2", + "cssnano": "^4.1.10", + "del": "^5.1.0", "eslint": "^6.8.0", "file-loader": "^5.0.2", + "gulp": "^4.0.2", + "gulp-babel": "^8.0.0", + "gulp-cli": "^2.2.0", + "gulp-concat": "^2.6.1", + "gulp-htmlmin": "^5.0.1", + "gulp-imagemin": "^7.1.0", + "gulp-inject": "^5.0.5", + "gulp-mode": "^1.0.2", + "gulp-postcss": "^8.0.0", + "gulp-sass": "^4.0.2", + "gulp-sourcemaps": "^2.6.5", + "gulp-terser": "^1.2.0", "html-webpack-plugin": "^3.2.0", + "node-sass": "^4.13.1", + "postcss-loader": "^3.0.0", + "postcss-preset-env": "^6.7.0", "style-loader": "^1.1.3", "stylelint": "^13.1.0", "stylelint-config-rational-order": "^0.1.2", @@ -20,10 +43,12 @@ "webpack-cli": "^3.3.10", "webpack-concat-plugin": "^3.0.0", "webpack-dev-server": "^3.10.3", - "webpack-merge": "^4.2.2" + "webpack-merge": "^4.2.2", + "webpack-stream": "^5.2.1" }, "dependencies": { "alameda": "^1.4.0", + "core-js": "^3.6.4", "document-register-element": "^1.14.3", "flv.js": "^1.5.0", "hls.js": "^0.13.1", @@ -43,6 +68,11 @@ "webcomponents.js": "^0.7.24", "whatwg-fetch": "^3.0.0" }, + "babel": { + "presets": [ + "@babel/preset-env" + ] + }, "browserslist": [ "last 2 Firefox versions", "last 2 Chrome versions", @@ -50,6 +80,7 @@ "last 2 Safari versions", "last 2 iOS versions", "last 2 Edge versions", + "Chrome 27", "Chrome 38", "Chrome 47", "Chrome 53", @@ -58,10 +89,10 @@ "Firefox ESR" ], "scripts": { - "serve": "webpack-dev-server --config webpack.dev.js --open", - "build": "webpack --config webpack.prod.js", + "serve": "gulp serve", + "build": "gulp --production", + "build dev": "gulp", "lint": "eslint \"src\"", - "stylelint": "stylelint \"src/**/*.css\"", - "prepare": "webpack --config webpack.prod.js" + "stylelint": "stylelint \"src/**/*.css\"" } } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 000000000..23159fd29 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,11 @@ +const postcssPresetEnv = require('postcss-preset-env'); +const cssnano = require('cssnano'); + +const config = () => ({ + plugins: [ + postcssPresetEnv(), + cssnano() + ] +}); + +module.exports = config diff --git a/src/bundle.js b/src/bundle.js index 5d05e4b68..5e1a2ab3e 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -97,6 +97,8 @@ _define("material-icons", function() { return material_icons; }); +// Noto Sans + var jellyfin_noto = require("jellyfin-noto"); _define("jellyfin-noto", function () { return jellyfin_noto; @@ -107,3 +109,8 @@ var page = require("page"); _define("page", function() { return page; }); + +var polyfill = require("@babel/polyfill/dist/polyfill"); +_define("polyfill", function () { + return polyfill; +}); diff --git a/src/index.html b/src/index.html index 8e9c8db3b..624be1936 100644 --- a/src/index.html +++ b/src/index.html @@ -105,5 +105,8 @@
+ + +