Implement web version of Heynote (#63)
* Move windows.darkMode -> window.heynote.themeMode * Only add UpdateStatusItem if window.heynote.autoUpdate is set * Update Vite * Implement web version of Heynote. Add a child vite project in ./webapp/ that is Heynote running within a browser. Imports almost all code from ../src/ and only adds a thin bridge that corresponds to the API between the Electron main process and the app code. * Remove commented out tag * Specify publicDir in vite config, instead of using a symlink * Add webapp_dev npm command to package.json * Add npm run command: webapp:build * Add resolve alias '@' that points to project root. Move assets file from public to assets in order to let Vite/Rollup handle bundling.
2
assets/font/open-sans/sass/_variables.scss
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
$OpenSansPath: "@/assets/font/open-sans/fonts" !default;
|
||||||
|
$OpenSansVersion: "1.1.0" !default;
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 583 B After Width: | Height: | Size: 583 B |
Before Width: | Height: | Size: 727 B After Width: | Height: | Size: 727 B |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 267 B After Width: | Height: | Size: 267 B |
@ -1,5 +1,5 @@
|
|||||||
const { contextBridge } = require('electron')
|
const { contextBridge } = require('electron')
|
||||||
import darkMode from "./theme-mode"
|
import themeMode from "./theme-mode"
|
||||||
import { isMac, isWindows, isLinux } from "../detect-platform"
|
import { isMac, isWindows, isLinux } from "../detect-platform"
|
||||||
import { ipcRenderer } from "electron"
|
import { ipcRenderer } from "electron"
|
||||||
import {
|
import {
|
||||||
@ -18,8 +18,6 @@ import {
|
|||||||
import CONFIG from "../config"
|
import CONFIG from "../config"
|
||||||
import getCurrencyData from "./currency"
|
import getCurrencyData from "./currency"
|
||||||
|
|
||||||
//contextBridge.exposeInMainWorld("platform", )
|
|
||||||
contextBridge.exposeInMainWorld("darkMode", darkMode)
|
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("heynote", {
|
contextBridge.exposeInMainWorld("heynote", {
|
||||||
platform: {
|
platform: {
|
||||||
@ -28,6 +26,8 @@ contextBridge.exposeInMainWorld("heynote", {
|
|||||||
isLinux,
|
isLinux,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
themeMode: themeMode,
|
||||||
|
|
||||||
quit() {
|
quit() {
|
||||||
console.log("quitting")
|
console.log("quitting")
|
||||||
//ipcRenderer.invoke("app_quit")
|
//ipcRenderer.invoke("app_quit")
|
||||||
|
@ -2,7 +2,7 @@ const { ipcRenderer } = require('electron')
|
|||||||
|
|
||||||
const getComputedTheme = () => window.matchMedia('(prefers-color-scheme: dark)').matches ? "dark" : "light"
|
const getComputedTheme = () => window.matchMedia('(prefers-color-scheme: dark)').matches ? "dark" : "light"
|
||||||
const mediaMatch = window.matchMedia('(prefers-color-scheme: dark)')
|
const mediaMatch = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
let darkModeChangeListener = null
|
let themeModeChangeListener = null
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
set: (mode) => ipcRenderer.invoke('dark-mode:set', mode),
|
set: (mode) => ipcRenderer.invoke('dark-mode:set', mode),
|
||||||
@ -14,14 +14,14 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onChange: (callback) => {
|
onChange: (callback) => {
|
||||||
darkModeChangeListener = (event) => {
|
themeModeChangeListener = (event) => {
|
||||||
callback(event.matches ? "dark" : "light")
|
callback(event.matches ? "dark" : "light")
|
||||||
}
|
}
|
||||||
mediaMatch.addEventListener('change', darkModeChangeListener)
|
mediaMatch.addEventListener('change', themeModeChangeListener)
|
||||||
return mediaMatch
|
return mediaMatch
|
||||||
},
|
},
|
||||||
removeListener() {
|
removeListener() {
|
||||||
mediaMatch.removeEventListener('change', darkModeChangeListener)
|
mediaMatch.removeEventListener('change', themeModeChangeListener)
|
||||||
},
|
},
|
||||||
initial: getComputedTheme(),
|
initial: getComputedTheme(),
|
||||||
}
|
}
|
||||||
|
2
package-lock.json
generated
@ -48,7 +48,7 @@
|
|||||||
"rollup-plugin-license": "^3.0.1",
|
"rollup-plugin-license": "^3.0.1",
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.57.1",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"vite": "^4.0.5",
|
"vite": "^4.5.1",
|
||||||
"vite-plugin-electron": "^0.11.1",
|
"vite-plugin-electron": "^0.11.1",
|
||||||
"vite-plugin-electron-renderer": "^0.11.4",
|
"vite-plugin-electron-renderer": "^0.11.4",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
|
@ -24,7 +24,9 @@
|
|||||||
"build": "vue-tsc --noEmit && vite build && electron-builder -c electron-builder.json5",
|
"build": "vue-tsc --noEmit && vite build && electron-builder -c electron-builder.json5",
|
||||||
"prebuild": "vue-tsc --noEmit && vite build",
|
"prebuild": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"build_grammar": "lezer-generator src/editor/lang-heynote/heynote.grammar -o src/editor/lang-heynote/parser.js"
|
"build_grammar": "lezer-generator src/editor/lang-heynote/heynote.grammar -o src/editor/lang-heynote/parser.js",
|
||||||
|
"webapp:dev": "vite webapp",
|
||||||
|
"webapp:build": "vite build webapp"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@codemirror/autocomplete": "^6.11.1",
|
"@codemirror/autocomplete": "^6.11.1",
|
||||||
@ -63,7 +65,7 @@
|
|||||||
"rollup-plugin-license": "^3.0.1",
|
"rollup-plugin-license": "^3.0.1",
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.57.1",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"vite": "^4.0.5",
|
"vite": "^4.5.1",
|
||||||
"vite-plugin-electron": "^0.11.1",
|
"vite-plugin-electron": "^0.11.1",
|
||||||
"vite-plugin-electron-renderer": "^0.11.4",
|
"vite-plugin-electron-renderer": "^0.11.4",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
$OpenSansPath: "font/open-sans/fonts" !default;
|
|
||||||
$OpenSansVersion: "1.1.0" !default;
|
|
@ -19,8 +19,8 @@
|
|||||||
selectionSize: 0,
|
selectionSize: 0,
|
||||||
language: "plaintext",
|
language: "plaintext",
|
||||||
languageAuto: true,
|
languageAuto: true,
|
||||||
theme: window.darkMode.initial,
|
theme: window.heynote.themeMode.initial,
|
||||||
initialTheme: window.darkMode.initial,
|
initialTheme: window.heynote.themeMode.initial,
|
||||||
systemTheme: 'system',
|
systemTheme: 'system',
|
||||||
development: window.location.href.indexOf("dev=1") !== -1,
|
development: window.location.href.indexOf("dev=1") !== -1,
|
||||||
showLanguageSelector: false,
|
showLanguageSelector: false,
|
||||||
@ -30,13 +30,20 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
window.darkMode.get().then((mode) => {
|
window.heynote.themeMode.get().then((mode) => {
|
||||||
this.theme = mode.computed
|
this.theme = mode.computed
|
||||||
this.systemTheme = mode.theme
|
this.systemTheme = mode.theme
|
||||||
})
|
})
|
||||||
window.darkMode.onChange((theme) => {
|
const onChangeCallback = (theme) => {
|
||||||
this.theme = theme
|
this.theme = theme
|
||||||
})
|
if (theme === "system") {
|
||||||
|
document.body.setAttribute("theme", window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light")
|
||||||
|
} else {
|
||||||
|
document.body.setAttribute("theme", theme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onChangeCallback(window.heynote.themeMode.initial)
|
||||||
|
window.heynote.themeMode.onChange(onChangeCallback)
|
||||||
window.heynote.onSettingsChange((settings) => {
|
window.heynote.onSettingsChange((settings) => {
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
})
|
})
|
||||||
@ -46,7 +53,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
window.darkMode.removeListener()
|
window.heynote.themeMode.removeListener()
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@ -66,7 +73,7 @@
|
|||||||
} else {
|
} else {
|
||||||
newTheme = this.systemTheme === "system" ? "light" : (this.systemTheme === "light" ? "dark" : "system")
|
newTheme = this.systemTheme === "system" ? "light" : (this.systemTheme === "light" ? "dark" : "system")
|
||||||
}
|
}
|
||||||
window.darkMode.set(newTheme)
|
window.heynote.themeMode.set(newTheme)
|
||||||
this.systemTheme = newTheme
|
this.systemTheme = newTheme
|
||||||
this.$refs.editor.focus()
|
this.$refs.editor.focus()
|
||||||
},
|
},
|
||||||
|
@ -50,6 +50,10 @@
|
|||||||
changeLanguageTitle() {
|
changeLanguageTitle() {
|
||||||
return `Change language for current block (${this.cmdKey} + L)`
|
return `Change language for current block (${this.cmdKey} + L)`
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updatesEnabled() {
|
||||||
|
return !!window.heynote.autoUpdate
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -80,7 +84,7 @@
|
|||||||
>
|
>
|
||||||
<span class="icon icon-format"></span>
|
<span class="icon icon-format"></span>
|
||||||
</div>
|
</div>
|
||||||
<UpdateStatusItem :allowBetaVersions="allowBetaVersions" />
|
<UpdateStatusItem v-if="updatesEnabled" :allowBetaVersions="allowBetaVersions" />
|
||||||
<div class="status-block theme clickable" @click="$emit('toggleTheme')" title="Toggle dark/light mode">
|
<div class="status-block theme clickable" @click="$emit('toggleTheme')" title="Toggle dark/light mode">
|
||||||
<span :class="'icon ' + systemTheme"></span>
|
<span :class="'icon ' + systemTheme"></span>
|
||||||
</div>
|
</div>
|
||||||
@ -148,11 +152,11 @@
|
|||||||
+dark-mode
|
+dark-mode
|
||||||
opacity: 0.9
|
opacity: 0.9
|
||||||
&.dark
|
&.dark
|
||||||
background-image: url("icons/dark-mode.png")
|
background-image: url("@/assets/icons/dark-mode.png")
|
||||||
&.light
|
&.light
|
||||||
background-image: url("icons/light-mode.png")
|
background-image: url("@/assets/icons/light-mode.png")
|
||||||
&.system
|
&.system
|
||||||
background-image: url("icons/both-mode.png")
|
background-image: url("@/assets/icons/both-mode.png")
|
||||||
|
|
||||||
.format
|
.format
|
||||||
padding-top: 0
|
padding-top: 0
|
||||||
@ -166,6 +170,6 @@
|
|||||||
background-size: 16px
|
background-size: 16px
|
||||||
background-repeat: no-repeat
|
background-repeat: no-repeat
|
||||||
background-position: center center
|
background-position: center center
|
||||||
background-image: url("icons/format.svg")
|
background-image: url("@/assets/icons/format.svg")
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -176,14 +176,14 @@
|
|||||||
background-size: 14px
|
background-size: 14px
|
||||||
background-repeat: no-repeat
|
background-repeat: no-repeat
|
||||||
background-position: center center
|
background-position: center center
|
||||||
background-image: url("icons/update.svg")
|
background-image: url("@/assets/icons/update.svg")
|
||||||
animation-name: spin
|
animation-name: spin
|
||||||
animation-duration: 2000ms
|
animation-duration: 2000ms
|
||||||
animation-iteration-count: infinite
|
animation-iteration-count: infinite
|
||||||
animation-timing-function: linear
|
animation-timing-function: linear
|
||||||
animation-play-state: paused
|
animation-play-state: paused
|
||||||
&.icon-download
|
&.icon-download
|
||||||
background-image: url("icons/download.svg")
|
background-image: url("@/assets/icons/download.svg")
|
||||||
width: 16px
|
width: 16px
|
||||||
height: 16px
|
height: 16px
|
||||||
background-size: 16px
|
background-size: 16px
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
@font-face
|
@font-face
|
||||||
font-family: 'Hack'
|
font-family: 'Hack'
|
||||||
src: url('font/hack/hack-regular.woff2') format('woff2'), url('font/hack/hack-regular.woff') format('woff')
|
src: url('@/assets/font/hack/hack-regular.woff2') format('woff2'), url('@/assets/font/hack/hack-regular.woff') format('woff')
|
||||||
font-weight: 400
|
font-weight: 400
|
||||||
font-style: normal
|
font-style: normal
|
||||||
|
|
||||||
|
|
||||||
@font-face
|
@font-face
|
||||||
font-family: 'Hack'
|
font-family: 'Hack'
|
||||||
src: url('font/hack/hack-bold.woff2') format('woff2'), url('font/hack/hack-bold.woff') format('woff')
|
src: url('@/assets/font/hack/hack-bold.woff2') format('woff2'), url('@/assets/font/hack/hack-bold.woff') format('woff')
|
||||||
font-weight: 700
|
font-weight: 700
|
||||||
font-style: normal
|
font-style: normal
|
||||||
|
|
||||||
@font-face
|
@font-face
|
||||||
font-family: 'Hack'
|
font-family: 'Hack'
|
||||||
src: url('font/hack/hack-italic.woff2') format('woff2'), url('font/hack/hack-italic.woff') format('woff')
|
src: url('@/assets/font/hack/hack-italic.woff2') format('woff2'), url('@/assets/font/hack/hack-italic.woff') format('woff')
|
||||||
font-weight: 400
|
font-weight: 400
|
||||||
font-style: italic
|
font-style: italic
|
||||||
|
|
||||||
@font-face
|
@font-face
|
||||||
font-family: 'Hack'
|
font-family: 'Hack'
|
||||||
src: url('font/hack/hack-bolditalic.woff2') format('woff2'), url('font/hack/hack-bolditalic.woff') format('woff')
|
src: url('@/assets/font/hack/hack-bolditalic.woff2') format('woff2'), url('@/assets/font/hack/hack-bolditalic.woff') format('woff')
|
||||||
font-weight: 700
|
font-weight: 700
|
||||||
font-style: italic
|
font-style: italic
|
||||||
|
|
||||||
|
|
||||||
@import "~/../public/font/open-sans/open-sans"
|
@import "@/assets/font/open-sans/open-sans"
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
=dark-mode()
|
=dark-mode()
|
||||||
@media (prefers-color-scheme: dark)
|
@media (prefers-color-scheme: dark)
|
||||||
@content
|
@content
|
||||||
|
body[theme=dark] &
|
||||||
|
@content
|
||||||
|
@ -6,6 +6,7 @@ import electron from 'vite-plugin-electron'
|
|||||||
import renderer from 'vite-plugin-electron-renderer'
|
import renderer from 'vite-plugin-electron-renderer'
|
||||||
import license from 'rollup-plugin-license'
|
import license from 'rollup-plugin-license'
|
||||||
import pkg from './package.json'
|
import pkg from './package.json'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
rmSync('dist-electron', { recursive: true, force: true })
|
rmSync('dist-electron', { recursive: true, force: true })
|
||||||
|
|
||||||
@ -14,6 +15,12 @@ const isProduction = process.env.NODE_ENV === "production"
|
|||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
electron([
|
electron([
|
||||||
|
80
webapp/bridge.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
const mediaMatch = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
|
let themeModeChangeListener = null
|
||||||
|
let autoUpdateCallbacks = null
|
||||||
|
let themeCallback = null
|
||||||
|
|
||||||
|
let currencyData = null
|
||||||
|
|
||||||
|
export default {
|
||||||
|
platform: {
|
||||||
|
isMac: true,
|
||||||
|
isWindows: false,
|
||||||
|
isLinux: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
buffer: {
|
||||||
|
async load() {
|
||||||
|
const content = localStorage.getItem("buffer")
|
||||||
|
return content === null ? "\n∞∞∞text-a\n" : content
|
||||||
|
},
|
||||||
|
|
||||||
|
async save(content) {
|
||||||
|
console.log("saving buffer")
|
||||||
|
localStorage.setItem("buffer", content)
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveAndQuit(content) {
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
onWindowClose(callback) {
|
||||||
|
//ipcRenderer.on(WINDOW_CLOSE_EVENT, callback)
|
||||||
|
},
|
||||||
|
|
||||||
|
onOpenSettings(callback) {
|
||||||
|
//ipcRenderer.on(OPEN_SETTINGS_EVENT, callback)
|
||||||
|
},
|
||||||
|
|
||||||
|
onSettingsChange(callback) {
|
||||||
|
//ipcRenderer.on(SETTINGS_CHANGE_EVENT, (event, settings) => callback(settings))
|
||||||
|
},
|
||||||
|
|
||||||
|
themeMode: {
|
||||||
|
set: (mode) => {
|
||||||
|
themeCallback(mode)
|
||||||
|
document.body.setAttribute("theme", mode === "dark" ? "dark" : "light")
|
||||||
|
},
|
||||||
|
get: async () => {
|
||||||
|
return {
|
||||||
|
theme: "light",
|
||||||
|
computed: "light",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChange: (callback) => {
|
||||||
|
themeCallback = callback
|
||||||
|
themeModeChangeListener = (event) => {
|
||||||
|
callback(event.matches ? "dark" : "light")
|
||||||
|
}
|
||||||
|
mediaMatch.addEventListener('change', themeModeChangeListener)
|
||||||
|
return mediaMatch
|
||||||
|
},
|
||||||
|
removeListener() {
|
||||||
|
mediaMatch.removeEventListener('change', themeModeChangeListener)
|
||||||
|
},
|
||||||
|
initial: "light",
|
||||||
|
},
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
keymap: "default",
|
||||||
|
},
|
||||||
|
|
||||||
|
getCurrencyData: async () => {
|
||||||
|
if (currencyData !== null) {
|
||||||
|
return currencyData
|
||||||
|
}
|
||||||
|
const response = await fetch("https://currencies.heynote.com/rates.json", {cache: "no-cache"})
|
||||||
|
currencyData = JSON.parse(await response.text())
|
||||||
|
return currencyData
|
||||||
|
},
|
||||||
|
}
|
21
webapp/index.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="color-scheme" content="light dark">
|
||||||
|
<title>Heynote</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import heynote from "./bridge.js";
|
||||||
|
window.heynote = heynote;
|
||||||
|
</script>
|
||||||
|
<script type="module" src="main.js"></script>
|
||||||
|
<script src="./math.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
13
webapp/main.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import '../src/css/application.sass'
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from '../src/components/App.vue'
|
||||||
|
import { loadCurrencies } from '../src/currency'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
app.mount('#app')
|
||||||
|
//console.log("test:", app.hej.test)
|
||||||
|
|
||||||
|
// load math.js currencies
|
||||||
|
loadCurrencies()
|
||||||
|
setInterval(loadCurrencies, 1000 * 3600 * 4)
|
54
webapp/vite.config.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
//import { directoryPlugin } from "vite-plugin-list-directory-contents";
|
||||||
|
|
||||||
|
|
||||||
|
const middleware = () => {
|
||||||
|
return {
|
||||||
|
name: 'middleware',
|
||||||
|
apply: 'serve',
|
||||||
|
configureServer(viteDevServer) {
|
||||||
|
return () => {
|
||||||
|
viteDevServer.middlewares.use(async (req, res, next) => {
|
||||||
|
console.log("url:", req.originalUrl)
|
||||||
|
if (!req.originalUrl.endsWith(".html") && req.originalUrl !== "/") {
|
||||||
|
req.url = `/src` + req.originalUrl + ".html";
|
||||||
|
} else if (req.url === "/index.html") {
|
||||||
|
//req.url = `/src` + req.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
publicDir: "../public",
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
//directoryPlugin({ baseDir: __dirname }),
|
||||||
|
],
|
||||||
|
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
sass: {
|
||||||
|
additionalData: `
|
||||||
|
@import "../src/css/include.sass"
|
||||||
|
`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, '..'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|