├── .gitignore ├── LICENSE ├── README.md ├── mytheme.libraries.yml ├── mytheme.theme ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── behaviors │ ├── gallery.behavior.js │ └── menu.behavior.js ├── gallery.js ├── global.js └── styles │ ├── _global.scss │ └── _menu.scss └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Dependency directories 15 | node_modules/ 16 | 17 | # Optional npm cache directory 18 | .npm 19 | 20 | # Optional eslint cache 21 | .eslintcache 22 | 23 | # Optional REPL history 24 | .node_repl_history 25 | 26 | # Output of 'npm pack' 27 | *.tgz 28 | 29 | # Compiled assets 30 | dist -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jan Hug 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drupal Webpack Setup Example 2 | This repository provides a simple example how to build JS and CSS assets with webpack for a Drupal theme. 3 | It uses Drupal's own behavior system, supports Hot Module Replacement (HMR) and compilation of SCSS 4 | (in local dev mode by injection from JavaScript). In production/build mode, CSS is extracted in files. 5 | 6 | ## Setup 7 | 8 | ``` 9 | npm install 10 | ``` 11 | 12 | To start the dev server, run: 13 | ``` 14 | npm run start 15 | ``` 16 | 17 | The server runs at http://localhost:9000. But if you follow the configuration in `mytheme.libraries.yml`, 18 | you can use it via your local Drupal URL, something like mydrupalsite.local. Because we define the 19 | webpack dev server URL as an external library in Drupal, features like Hot Module Replacement will 20 | work. 21 | 22 | 23 | [Webpack loader for Drupal behaviors](https://github.com/dulnan/drupal-behaviors-loader) 24 | [Webpack plugin for Drupal.t and Drupal.formatPlural translations](https://github.com/dulnan/drupal-behaviors-loader) -------------------------------------------------------------------------------- /mytheme.libraries.yml: -------------------------------------------------------------------------------- 1 | global-styling: 2 | css: 3 | theme: {} 4 | js: 5 | dist/drupalTranslations.js 6 | dependencies: 7 | - core/modernizr 8 | - core/Drupal 9 | - core/drupalSettings 10 | - core/drupalTranslations 11 | 12 | # 13 | # Global 14 | # 15 | global-build: 16 | css: 17 | theme: 18 | dist/css/global.css: { minified: true, preprocess: false } 19 | js: 20 | dist/global.js: { minified: true, preprocess: false, scope: footer } 21 | 22 | global-dev: 23 | js: 24 | http://localhost:9000/themes/mytheme/global.js: { type: external, minified: true, preprocess: false } 25 | 26 | # 27 | # Gallery 28 | # 29 | gallery-build: 30 | js: 31 | dist/gallery.js: { minified: true, preprocess: false, scope: footer } 32 | 33 | gallery-dev: 34 | js: 35 | http://localhost:9000/themes/mytheme/gallery.js: { type: external, minified: true, preprocess: false } 36 | -------------------------------------------------------------------------------- /mytheme.theme: -------------------------------------------------------------------------------- 1 | this._handleClick()) 25 | 26 | this._element.appendChild(this._button) 27 | 28 | document.body.appendChild(this._element) 29 | }, 30 | 31 | detach () { 32 | this._button.removeEventListener('click', () => this._handleClick()) 33 | this._element.parentNode.removeChild(this._element) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/gallery.js: -------------------------------------------------------------------------------- 1 | import './behaviors/gallery.behavior' 2 | 3 | if (module.hot) { 4 | module.hot.accept() 5 | } -------------------------------------------------------------------------------- /src/global.js: -------------------------------------------------------------------------------- 1 | import '@/styles/_global.scss' 2 | 3 | import './behaviors/menu.behavior' 4 | 5 | if (module.hot) { 6 | module.hot.accept() 7 | } 8 | -------------------------------------------------------------------------------- /src/styles/_global.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #fbfbfb; 3 | color: #444; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/styles/_menu.scss: -------------------------------------------------------------------------------- 1 | .my-test-class { 2 | padding: 2rem; 3 | background: #eee; 4 | border: 1px solid #ddd; 5 | 6 | button { 7 | appearance: none; 8 | font-weight: bold; 9 | font-size: 1.25rem; 10 | padding: 1.5rem; 11 | background: white; 12 | box-shadow: 0 2px 4px rgba(black, 0.12); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 5 | const DrupalTranslationsWebpackPlugin = require('drupal-translations-webpack-plugin') 6 | 7 | const LOCAL_PORT = 9000 8 | 9 | module.exports = env => { 10 | const isProduction = env ? env.production : false 11 | 12 | let output = { 13 | publicPath: isProduction ? '/themes/mytheme' : `http://localhost:${LOCAL_PORT}/themes/mytheme` 14 | } 15 | 16 | // Define two entries: One for global styling and behaviors, 17 | // the other for an imaginary gallery component. 18 | let entry = { 19 | global: './src/global.js', 20 | gallery: './src/gallery.js' 21 | } 22 | 23 | let resolve = { 24 | extensions: ['.js', '.vue', '.json'], 25 | alias: { 26 | '@': path.join(__dirname, 'src') 27 | } 28 | } 29 | 30 | let module = { 31 | rules: [ 32 | { 33 | // Only load files that match *.behavior.js 34 | test: /\.behavior.js$/, 35 | 36 | // Define the loader to use. 37 | loader: 'drupal-behaviors-loader', 38 | 39 | // Exclude node_modules folder. 40 | exclude: /node_modules/, 41 | 42 | // Optionally define a folder to include specifically. 43 | // include: /js\/behaviors/, 44 | 45 | // Set the options. Depending on the env, enable 46 | // or disable injection of the HMR code. 47 | options: { 48 | enableHmr: !isProduction 49 | }, 50 | }, 51 | 52 | // This will transpile our JS files using Babel. 53 | { 54 | test: /\.js$/, 55 | exclude: /node_modules/, 56 | use: { 57 | loader: 'babel-loader' 58 | } 59 | }, 60 | 61 | // If you want to also compile SCSS files, add this loader. 62 | { 63 | test: /\.scss$/, 64 | use: [ 65 | isProduction ? MiniCssExtractPlugin.loader : 'style-loader', 66 | 'css-loader', 67 | 'sass-loader', 68 | 'postcss-loader' 69 | ] 70 | } 71 | ] 72 | } 73 | 74 | let plugins = [] 75 | 76 | // Only extract CSS when building assets for production. 77 | if (isProduction) { 78 | plugins.push(new MiniCssExtractPlugin({ 79 | // For more information and options, check out 80 | // https://github.com/webpack-contrib/sass-loader#in-production 81 | filename: 'css/[name].css', 82 | chunkFilename: 'css/[id].css' 83 | })) 84 | 85 | plugins.push(new DrupalTranslationsWebpackPlugin()) 86 | } 87 | 88 | // Setting up webpack devServer 89 | let devServer = { 90 | contentBase: path.join(__dirname, 'dist'), 91 | compress: true, 92 | port: LOCAL_PORT, 93 | // A more restrictive setting is recommended. 94 | headers: { 95 | 'Access-Control-Allow-Origin': '*' 96 | } 97 | } 98 | 99 | return { output, entry, resolve, module, plugins, devServer } 100 | } 101 | --------------------------------------------------------------------------------