Skip to content

WIP: Add webpack, auto compile JS. #200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"plugins": ["syntax-dynamic-import"],
"presets": [
[
"@babel/preset-env",
{
"modules": false
}
]
]
}
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -96,6 +96,8 @@
*.css text
*.htm text diff=html
*.html text diff=html
*.j2 text diff=html
*.jinja text diff=html
*.inc text
*.ini text
*.js text
2 changes: 1 addition & 1 deletion lms/babel.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[python: **.py]
[jinja2: **/templates/**.html]
[jinja2: **/templates/dist/**.j2]
4 changes: 2 additions & 2 deletions lms/lmsweb/__init__.py
Original file line number Diff line number Diff line change
@@ -9,8 +9,8 @@

project_dir = pathlib.Path(__file__).resolve().parent.parent
web_dir = project_dir / 'lmsweb'
template_dir = project_dir / 'templates'
static_dir = project_dir / 'static'
template_dir = project_dir / 'templates' / 'dist'
static_dir = project_dir / 'static' / 'dist'
config_file = web_dir / 'config.py'
config_example_file = web_dir / 'config.py.example'

19 changes: 8 additions & 11 deletions lms/lmsweb/views.py
Original file line number Diff line number Diff line change
@@ -112,7 +112,7 @@ def login():
return fail(400, "The URL isn't safe.")
return redirect(next_url or url_for('main'))

return render_template('login.html')
return render_template('login.j2')


@webapp.route('/logout')
@@ -137,7 +137,7 @@ def banned_page():
current_user.is_authenticated
and current_user.role.is_banned
):
return render_template('banned.html')
return render_template('banned.j2')


@webapp.route('/')
@@ -150,10 +150,7 @@ def main():
@managers_only
@login_required
def status():
return render_template(
'status.html',
exercises=Solution.status(),
)
return render_template('status.j2', exercises=Solution.status())


@webapp.route('/exercises')
@@ -163,7 +160,7 @@ def exercises_page():
exercises = Solution.of_user(current_user.id, fetch_archived)
is_manager = current_user.role.is_manager
return render_template(
'exercises.html',
'exercises.j2',
exercises=exercises,
is_manager=is_manager,
fetch_archived=fetch_archived,
@@ -307,7 +304,7 @@ def comment():
@webapp.route('/send/<int:_exercise_id>')
@login_required
def send(_exercise_id):
return render_template('upload.html')
return render_template('upload.j2')


@webapp.route('/user/<int:user_id>')
@@ -320,7 +317,7 @@ def user(user_id):
return fail(404, 'There is no such user.')

return render_template(
'user.html',
'user.j2',
solutions=Solution.of_user(target_user.id, with_archived=True),
user=target_user,
)
@@ -329,7 +326,7 @@ def user(user_id):
@webapp.route('/send', methods=['GET'])
@login_required
def send_():
return render_template('upload.html')
return render_template('upload.j2')


@webapp.route('/upload', methods=['POST'])
@@ -454,7 +451,7 @@ def view(
if viewer_is_solver:
notifications.read_related(solution_id, current_user.id)

return render_template('view.html', **view_params)
return render_template('view.j2', **view_params)


@webapp.route(f'{routes.SHARED}/<string:shared_url>')
8 changes: 5 additions & 3 deletions lms/templates/banned.html → lms/templates/banned.j2
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
{% extends 'base.html' %}
{% extends 'base.j2' %}

{% block head_scripts %}<%= htmlWebpackPlugin.tags.headTags %>{% endblock %}
{% block page_content %}
<div class="container">
<div id="login-container">
<div id="login" class="text-center">
<div id="course-logo" style="opacity: 0.6">
<div id="course-logo">
<img id="login-logo" src="{{ url_for('static', filename='avatar.jpg') }}" alt="{{ _('תמונת הפרופיל של קורס פייתון') }}" width="144" height="144">
</div>
<h1 id="main-title" class="h3 font-weight-normal">השעייה</h1>
<h1 id="main-title" class="h3 font-weight-normal">{{ _('הושעת מהמערכת') }}</h1>
<p>
{{ _('המשתמש שלך הושעה על ידי מנהל המערכת.') }}<br>
{{ _('לפרטים נוספים אנא פנה אל צוות הניהול.') }}
</p>
</div>
</div>
</div>
<%= htmlWebpackPlugin.tags.bodyTags %>
{% endblock %}
21 changes: 0 additions & 21 deletions lms/templates/base.html

This file was deleted.

13 changes: 13 additions & 0 deletions lms/templates/base.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en" class="{{ direction }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ _('מערכת הגשת תרגילים לקורס פייתון') }}</title>
{% block head_scripts %}{% endblock %}
</head>
<body>
{% include 'navbar.j2' %}
{% block page_content %}{% endblock %}
</body>
</html>
4 changes: 3 additions & 1 deletion lms/templates/exercises.html → lms/templates/exercises.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{%- extends 'base.html' -%}
{%- extends 'base.j2' -%}

{% block head_scripts %}<%= htmlWebpackPlugin.tags.headTags %>{% endblock %}
{%- block page_content -%}
<div class="container">
<div id="exercises-page" class="page {{ direction }}">
@@ -60,4 +61,5 @@ <h1 id="exercises-head">{{ _('תרגילים') }}</h1>
</div>
</div>
</div>
<%= htmlWebpackPlugin.tags.bodyTags %>
{%- endblock -%}
File renamed without changes.
4 changes: 3 additions & 1 deletion lms/templates/login.html → lms/templates/login.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{% extends 'base.html' %}
{% extends 'base.j2' %}

{% block head_scripts %}<%= htmlWebpackPlugin.tags.headTags %>{% endblock %}
{% block page_content %}
<div class="container">
<div id="login-container">
@@ -25,4 +26,5 @@ <h1 id="main-title" class="h3 font-weight-normal">{{ _('התחברות') }}</h1>
</div>
</div>
</div>
<%= htmlWebpackPlugin.tags.bodyTags %>
{% endblock %}
2 changes: 1 addition & 1 deletion lms/templates/navbar.html → lms/templates/navbar.j2
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="{{ config.HOME_URL or "/" }}">
<a class="navbar-brand" href="{{ config.HOME_URL or '/' }}">
<img src="{{ url_for('static', filename='avatar.jpg') }}" width="30" height="30" class="d-inline-block align-top" alt="{{ _('הלוגו של פרויקט לומדים פייתון: נחש צהוב על רקע עיגול בצבע תכלת, ומתחתיו כתוב - לומדים פייתון.') }}">
</a>
<div class="collapse navbar-collapse" id="navbarNavDropdown">
4 changes: 3 additions & 1 deletion lms/templates/status.html → lms/templates/status.j2
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

{% extends 'base.html' %}
{% extends 'base.j2' %}

{% block head_scripts %}<%= htmlWebpackPlugin.tags.headTags %>{% endblock %}
{% block page_content %}
<div id="page-status" class="{{ direction }}">
<div id="status-head">
@@ -44,4 +45,5 @@ <h1>{{ _('חמ"ל תרגילים') }}</h1>
</table>
</div>
</div>
<%= htmlWebpackPlugin.tags.bodyTags %>
{% endblock %}
6 changes: 3 additions & 3 deletions lms/templates/upload.html → lms/templates/upload.j2
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% extends 'base.html' %}
{% extends 'base.j2' %}

{% block head_scripts %}<%= htmlWebpackPlugin.tags.headTags %>{% endblock %}
{% block page_content %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.0/min/dropzone.min.css" integrity="sha256-AgL8yEmNfLtCpH+gYp9xqJwiDITGqcwAbI8tCfnY2lw=" crossorigin="anonymous" />
<div class="container">
<div id="send-page" class="page">
<h1 id="send-head" class="text-center">{{ _('העלאת מחברות') }}</h1>
@@ -13,7 +13,7 @@ <h1 id="send-head" class="text-center">{{ _('העלאת מחברות') }}</h1>
</form>
<a id="back-to-exercises" class="btn btn-primary btn-lg" href="/exercises">{{ _('חזרו לרשימת התרגילים') }}</a>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.0/min/dropzone.min.js" integrity="sha256-OG/103wXh6XINV06JTPspzNgKNa/jnP1LjPP5Y3XQDY=" crossorigin="anonymous"></script>
</div>
</div>
<%= htmlWebpackPlugin.tags.bodyTags %>
{% endblock %}
4 changes: 3 additions & 1 deletion lms/templates/user.html → lms/templates/user.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{% extends 'base.html' %}
{% extends 'base.j2' %}

{% block head_scripts %}<%= htmlWebpackPlugin.tags.headTags %>{% endblock %}
{% block page_content %}
<div id="page-user">
<div id="user" class="{{ direction }}">
@@ -42,4 +43,5 @@ <h2>{{ _('תרגילים שהוגשו:') }}</h2>
</div>
</div>
</div>
<%= htmlWebpackPlugin.tags.bodyTags %>
{% endblock %}
13 changes: 5 additions & 8 deletions lms/templates/view.html → lms/templates/view.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{% extends 'base.html' %}
{% extends 'base.j2' %}

{% block head_scripts %}<%= htmlWebpackPlugin.tags.headTags %>{% endblock %}
{% block page_content %}
<div id="page-view">
<div id="view-head">
@@ -119,7 +120,7 @@ <h2>{{ _('הערות בודק') }}</h2>
</div>
{% endif %}
{%- if files | length > 1 %}
{% include 'filetree.html' %}
{% include 'filetree.j2' %}
{% endif -%}
</div>
<a id="download-solution" href="/download/{% if shared_url %}{{ shared_url }}{% else %}{{ solution['id'] }}{% endif %}" class="{{ direction }}">
@@ -128,12 +129,8 @@ <h2>{{ _('הערות בודק') }}</h2>
</button>
</a>
</div>
<script src="{{ url_for('static', filename='prism.js') }}"></script>
{% if not shared_url %}
<script src="{{ url_for('static', filename='comments.js') }}"></script>
<%= htmlWebpackPlugin.tags.bodyTags[htmlWebpackPlugin.options.chunks.indexOf('view')] %>
{%- if is_manager %}
<script src="{{ url_for('static', filename='grader.js') }}"></script>
<script src="{{ url_for('static', filename='keyboard.js') }}"></script>
<%= htmlWebpackPlugin.tags.bodyTags[htmlWebpackPlugin.options.chunks.indexOf('view_admin')] %>
{% endif -%}
{% endif %}
{% endblock %}
32 changes: 30 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -6,12 +6,40 @@
"repository": "https://github.com/PythonFreeCourse/lms",
"author": "Yam Mesicka <yammesicka@gmail.com>",
"license": "BSD-3-Clause",
"dependencies": {},
"dependencies": {
"bootstrap": "^5.0.0-alpha2",
"bootstrap-icons": "^1.0.0",
"dropzone": "^5.7.2",
"popper.js": "^1.16.1"
},
"scripts": {
"build": "webpack --progress",
"dev-build": "webpack --progress --mode development",
"watch": "webpack --progress --mode development --watch"
},
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/preset-env": "^7.11.5",
"autoprefixer": "^10.0.1",
"babel-loader": "^8.1.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^4.3.0",
"eslint": "^7.10.0",
"eslint-config-airbnb-base": "^14.2.0",
"eslint-plugin-import": "^2.22.1",
"html-webpack-plugin": "^4.5.0",
"mini-css-extract-plugin": "^0.11.2",
"postcss": "^8.1.1",
"postcss-loader": "^4.0.2",
"precss": "^4.0.0",
"sass": "^1.26.11",
"sass-loader": "^10.0.2",
"style-loader": "^1.2.1",
"stylelint": "^13.7.2",
"stylelint-config-standard": "^20.0.0"
"stylelint-config-standard": "^20.0.0",
"terser-webpack-plugin": "^4.2.2",
"webpack": "^5.0.0-rc.3",
"webpack-cli": "^4.0.0-rc.0"
}
}
193 changes: 193 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
const path = require('path');
const webpack = require('webpack');
const fs = require('fs');

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const autoprefixer = require('autoprefixer');
const precss = require('precss');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');

const isProduction = process.argv.indexOf('production') !== -1;

const shared = [
path.resolve(__dirname, 'node_modules', 'bootstrap', 'scss', 'bootstrap.scss'),
'./my.css',
];

function findTemplates(dirPath) {
const dir = fs.opendirSync(dirPath);
const filenames = [];
let currentDir = dir.readSync();
while (currentDir !== null) {
if (currentDir.name.endsWith('.j2')) {
filenames.push(currentDir.name.slice(0, -3));
}
currentDir = dir.readSync();
}
dir.closeSync();
return filenames;
}

const templatesDir = path.resolve(__dirname, 'lms', 'templates');
const templates = findTemplates(templatesDir);
const specialTemplates = ['view'];
const autoCompile = templates.filter((x) => !specialTemplates.includes(x));
const pages = autoCompile.map(
(pageName) => new HtmlWebpackPlugin({
inject: false,
chunks: [pageName],
minify: isProduction,
template: path.join('..', 'templates', `${pageName}.j2`),
filename: path.join('..', '..', 'templates', 'dist', `${pageName}.j2`),
}),
);

const defaultPages = {};
autoCompile.forEach((page) => {
defaultPages[page] = { import: [...shared] };
});

module.exports = {
context: path.resolve(__dirname, 'lms', 'static'),
entry: {
...defaultPages,
upload: { import: [...shared, 'dropzone', './my.js'] },
view: {
import: [
...shared, './my.js', './prism.css', './prism.js', './comments.js',
],
},
view_admin: {
dependOn: 'view',
import: [
...shared, './my.js', './prism.css', './prism.js', './comments.js',
'./keyboard.js', './grader.js',
],
},
},

output: {
path: path.resolve(__dirname, 'lms', 'static', 'dist'),
publicPath: '/dist/',
filename: path.join('js', '[name].[chunkhash:8].js'),
chunkFilename: path.join('js', '[name].[chunkhash:8].chunk.js'),
},

plugins: [
new webpack.ProgressPlugin(),
new CleanWebpackPlugin(),
...pages,
new HtmlWebpackPlugin({
inject: false,
chunks: ['view', 'view_admin'],
minify: isProduction,
template: path.join('..', 'templates', 'view.j2'),
filename: path.join('..', '..', 'templates', 'dist', 'view.j2'),
}),
new MiniCssExtractPlugin({ filename: 'main.[chunkhash].css' }),
],

module: {
rules: [
{
test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: path.resolve(__dirname, 'lms', 'static', 'dist', 'fonts'),
publicPath: '/dist/fonts',
},
}],
},
{
test: /\.(js|jsx)$/,
include: [path.resolve(__dirname, 'lms', 'static')],
loader: 'babel-loader',
}, {
test: /\.s[ac]ss$/i,

use: [
{ loader: 'style-loader' },
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',
options: {
modules: false,
sourceMap: !isProduction,
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins() {
return [
autoprefixer,
];
},
},
},
},
{
loader: 'sass-loader',
options: {
sassOptions: {
sourceMap: !isProduction,
},
},
},
],
}, {
test: /\.css$/i,

use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',

options: {
importLoaders: 1,
sourceMap: true,
modules: { auto: true },
},
},
{
loader: 'postcss-loader',

options: {
postcssOptions: {
plugins() {
return [
precss,
autoprefixer,
];
},
},
},
},
],
}],
},
performance: { hints: false },
optimization: {
minimizer: [new TerserPlugin()],
// runtimeChunk: 'single',

splitChunks: {
cacheGroups: {
vendors: {
priority: -10,
test: /[\\/]node_modules[\\/]/,
},
},

chunks: 'async',
minChunks: 1,
minSize: 30000,
name: 'vendors', // TODO: true?
},
},
};
5,427 changes: 5,302 additions & 125 deletions yarn.lock

Large diffs are not rendered by default.