├── .babelrc ├── .gitignore ├── .postcssrc.js ├── README.md ├── __webp__.js ├── _sprites_src └── icons │ ├── a.png │ ├── b.png │ ├── c.png │ └── d.png ├── gulpfile.js ├── package-lock.json ├── package.json ├── sprites-css.handlebars ├── src ├── assets │ ├── _img │ │ ├── AbsolutePath.jpg │ │ ├── avatar.jpg │ │ ├── pngmin.png │ │ └── test.jpg │ ├── img │ │ ├── AbsolutePath.jpg │ │ ├── avatar.jpg │ │ ├── pngmin.png │ │ └── test.jpg │ └── sprites │ │ ├── icons.css │ │ └── icons.png ├── common_css_entry.js ├── css │ ├── _common │ │ └── common.scss │ ├── index │ │ ├── index.css │ │ └── index.scss │ └── test │ │ └── test.scss ├── global.scss ├── js │ ├── _modules │ │ ├── P.js │ │ └── async.js │ ├── a.js │ ├── about.js │ ├── index.js │ ├── index1.js │ ├── t.js │ ├── test │ │ ├── test-0.js │ │ └── test-1.js │ ├── vue.js │ └── vue │ │ └── App.vue ├── pages │ ├── _tpls │ │ └── a.pug │ ├── a.html │ ├── about.html │ ├── index.html │ ├── index1.pug │ ├── t.html │ ├── test │ │ ├── test-0.html │ │ └── test-1.pug │ └── vue.html └── readFileList.js ├── static ├── .gitkeep └── css │ └── static.css ├── webpack.cfg.js ├── webpack.common.js ├── webpack.dev.js ├── webpack.prod.js └── webpack.until.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "latest" 4 | ] 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | // "parser": "postcss-strip-inline-comments", 5 | "plugins": { 6 | // to edit target browsers: use "browserslist" field in package.json 7 | "autoprefixer": { 8 | browsers: ["last 10 versions","Android >= 4.0"] 9 | }, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [webpack-multi-page](https://github.com/lfyfly/webpack-multi-page) 2 | 3 | ## 1、快速使用 4 | ### 1.1 克隆项目 5 | ``` 6 | git clone https://github.com/lfyfly/webpack-multi-page.git 7 | ``` 8 | 删除`.git`文件夹,这是我的`commit记录`,所以删除 9 | 10 | ### 1.2 安装依赖 11 | ``` 12 | npm i 13 | ``` 14 | ### 1.3 进入开发模式 15 | ``` 16 | npm run dev 17 | ``` 18 | 19 | ### 1.4 打包 20 | ``` 21 | npm run build 22 | ``` 23 | 24 | ### 1.5 一键兼容webp图片 25 | 在执行完`npm run build`后执行`npm run webp` 26 | 27 | #### 默认情况下html中的`img[src]`会被处理成`img[data-src]` 28 | - 当img的src为`http`开头则会被忽略该处理 29 | - 当img的className中包含`not-webp`开头则会被忽略该处理 30 | 31 | ### 1.6 图片压缩 32 | `src/assets/_img`(原图文件夹) -> `src/assets/img`(压缩后图片文件夹) 33 | 34 | ``` 35 | npm run imgmin 36 | ``` 37 | ### 1.7 雪碧图 38 | `_sprites_src/xxx/*.png`(原图文件夹) -> `src/sprites/xxx.css` + `src/sprites/xxx.png` 39 | 40 | ``` 41 | npm run sp 42 | ``` 43 | ### 1.8 配置文件 44 | 详见根目录下`webpack.cfg.js` 45 | 46 | ## 2、功能简介 47 | ### 2.1 开发模式 48 | - 多页面开发,支持vue 49 | - 支持无需引入即可全局使用的`global.scss` 50 | - 支持px2rem 51 | - `src/pages`中的html(或pug)文件和`src/js`中的js(入口)文件,必须一一对应 52 | - 新增页面,需要重新运行`npm run dev` 53 | - html,css,js 更改自动刷新 54 | - scss,es6+,pug支持 55 | - 支持代理配置 56 | 57 | ### 2.2 关于图片资源 58 | - 图片不要放在`/static`文件下,而是放在`/assets`。 59 | - 因为html中img标签的`src`如果是绝对路径则会被定为到`src`目录下,无法引用到`static`目录下 60 | - css中图片如果以`/static`路径开头,会不经过`url-loader`所处理 61 | 62 | 63 | - html中的img标签`src`对应图片可以被`url-loader`所处理 64 | - 第一种方式是`相对html路径` 65 | - 第二种方式以`/assets`开头的绝对路径,自动定位到`src/assets`目录下 66 | - 第三种种方式以`~@/assets`开头的绝对路径,自动定位到`src/assets`目录下 67 | 68 | - csss中的背景图写法 69 | - 第一种方式是`相对css文件的相对路径` 70 | - 第二种种方式以`~@/assets`开头的绝对路径,自动定位到`src/assets`目录下 71 | 72 | ### 2.3 打包相关 73 | - 为了css文件图片路径完美生成`相对路径`,会被打包成奇怪的图片路径`../../static/img/xxx.jpg` 74 | - 打包cdn路径一键配置 75 | - 静态文件目录`static`文件夹,打包会被拷贝到dist目录 76 | - 支持打包文件版本hash,提取`vendor.js` `common.js` `[page].js`文件,只对模块更改的css,js文件版本hash进行更改 77 | - `vendor.js`是指`/node_modules`文件夹中引用的第三方插件 78 | - `common.js`是指被多个页面引用超过2次并且,大小超过20k时,才会生成 79 | - `[page.js]`对应着每个页面独自的js文件 80 | - css文件单独提取 81 | - 小于8k图片文件和字体文件自动转base64代码 82 | - pages多级目录支持(忽略下划线开头的文件和文件夹) 83 | - 当配置文件`webpack.cfg.js`中`build.assetsPublicPath === './' `,二级目录以上页面需要在页面中,增加``标签进行修正相对路径。如`src/pages/test/test-0.html`中的``中的`` 84 | - 当配置文件`webpack.cfg.js`中`build.assetsPublicPath === '/' `, 则路径为绝对路径,无需修正路径 85 | 86 | - 页面共有css文件入口支持 87 | ``` 88 | commonCss:{ 89 | entry: path.resolve(__dirname,'src/common_css_entry.js'), // String 必填,入口文件,绝对地址 90 | exclude:['about'] // Arrary 排除页面,不填所有页面都引入common_css 91 | // ['about'] 代表about页面不引用 common_css 92 | }, 93 | ``` 94 | ## gulp 多页面配置 95 | [gulp-easy](https://github.com/lfyfly/gulp-easy) 96 | -------------------------------------------------------------------------------- /__webp__.js: -------------------------------------------------------------------------------- 1 | 2 | (function () { 3 | function checkWebp() { 4 | try { 5 | return (document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') == 0) 6 | } catch (err) { 7 | return false 8 | } 9 | } 10 | var supportWebp = checkWebp() 11 | var htmlEl = document.querySelector('html') 12 | if (supportWebp) htmlEl.className = htmlEl.className + ' __webp__' 13 | window.addEventListener('DOMContentLoaded', function () { 14 | var imgEls = document.querySelectorAll('img[data-src]') 15 | for (var i = 0; i < imgEls.length; i++) { 16 | var imgSrc = imgEls[i].getAttribute('data-src') 17 | imgEls[i].removeAttribute('data-src') 18 | if (supportWebp) imgSrc = imgSrc.replace(/(\.[^\.]+)$/, '.webp') 19 | imgEls[i].src = imgSrc 20 | imgEls[i].style.visibility = 'visible' 21 | } 22 | }) 23 | })() 24 | 25 | -------------------------------------------------------------------------------- /_sprites_src/icons/a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfyfly/webpack-multi-page/ba3d91e492f0b6afb877676ecce056a0a7d500ff/_sprites_src/icons/a.png -------------------------------------------------------------------------------- /_sprites_src/icons/b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfyfly/webpack-multi-page/ba3d91e492f0b6afb877676ecce056a0a7d500ff/_sprites_src/icons/b.png -------------------------------------------------------------------------------- /_sprites_src/icons/c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfyfly/webpack-multi-page/ba3d91e492f0b6afb877676ecce056a0a7d500ff/_sprites_src/icons/c.png -------------------------------------------------------------------------------- /_sprites_src/icons/d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfyfly/webpack-multi-page/ba3d91e492f0b6afb877676ecce056a0a7d500ff/_sprites_src/icons/d.png -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | img_src: 'src/assets/_img', 3 | img_dest: 'src/assets/img', 4 | imgmin_quality: 70 5 | } 6 | const fs = require('fs') 7 | const gulp = require('gulp') 8 | const imagemin = require('gulp-imagemin') 9 | const mozjpeg = require('imagemin-mozjpeg') 10 | const pngquant = require('imagemin-pngquant') 11 | const cache = require('gulp-cache') // 缓存压缩图片,避免重复压缩 12 | 13 | gulp.task('imgmin', function () { 14 | gulp.src(`${config.img_src}/**/*.{jpg,jpeg,png}`) 15 | .pipe(cache(imagemin( 16 | [mozjpeg({ quality: config.imgmin_quality }), pngquant({ quality: config.imgmin_quality })]))) 17 | .pipe(gulp.dest(config.img_dest)) 18 | }) 19 | 20 | // webp 21 | // generate .webp image 22 | // html src -> data-src 23 | // css -> add .__webp__ className 24 | // append __webp__.js 25 | 26 | gulp.task('webp', ['generateWebp', 'webpcss', 'webphtml']) 27 | 28 | const generateWebp = require('gulp-webp') 29 | gulp.task('generateWebp', function () { 30 | gulp.src('dist/**/*.{png,jpg,jpeg}') 31 | .pipe(generateWebp()) 32 | .pipe(gulp.dest('./dist')) 33 | }) 34 | 35 | const webpcss = require('gulp-webpcss') 36 | const cssnano = require('gulp-cssnano'); 37 | gulp.task('webpcss', function () { 38 | gulp.src("dist/**/*.css") 39 | .pipe(webpcss({ 40 | webpClass: '.__webp__', 41 | replace_from: /\.(png|jpg|jpeg)/, 42 | replace_to: '.webp', 43 | })) 44 | .pipe(cssnano()) 45 | .pipe(gulp.dest("./dist")) 46 | }) 47 | 48 | const cheerio = require('gulp-cheerio') 49 | gulp.task('webphtml', function () { 50 | return gulp 51 | .src('dist/**/*.html') 52 | .pipe(cheerio(function ($, file) { 53 | // 插入webp.js 54 | 55 | var webpJs = fs.readFileSync('__webp__.js', 'utf-8'); 56 | $('head').append(``) 57 | 58 | $('img[src]:not(.not-webp)').each(function () { 59 | var imgEl = $(this) 60 | var src = imgEl.attr('src') 61 | if (/^http|\.(gif|svg)$/.test(src)) return 62 | imgEl.css('visibility','hidden') 63 | imgEl.removeAttr('src') 64 | imgEl.attr('data-src', src) 65 | }) 66 | 67 | if ($('#__webp__').length > 0) return 68 | })) 69 | .pipe(gulp.dest('dist')) 70 | }) 71 | 72 | 73 | // 雪碧图 74 | // _ 75 | const spritesmith = require('gulp.spritesmith') 76 | const gulpIf = require('gulp-if') 77 | var buffer = require('vinyl-buffer'); 78 | gulp.task('sprites', function () { 79 | // 读取 sprites 80 | let spritesList = fs.readdirSync('_sprites_src') 81 | let sprites = gulp.src('_sprites_src/*/*.{jpg,png}') 82 | spritesList.forEach((spritesItem) => { 83 | sprites = sprites.pipe(gulpIf(`${spritesItem}/*.{jpg,png,svg}`, spritesmith({ 84 | imgName: spritesItem + '.png', 85 | cssName: spritesItem + '.css', 86 | cssTemplate:'sprites-css.handlebars', 87 | imgPath: `./${spritesItem}.png` 88 | }))) 89 | }) 90 | return sprites 91 | .pipe(buffer()) 92 | .pipe(imagemin( 93 | [mozjpeg({ quality: config.imgmin_quality }), pngquant({ quality: config.imgmin_quality })])) 94 | .pipe(gulp.dest('src/assets/sprites')) 95 | }) 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-multi-page", 3 | "version": "1.2.0", 4 | "description": "webpack webpack4 multi-page multiPage multiplePage", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack-dev-server --config webpack.dev.js --mode development", 8 | "build": "webpack --config webpack.prod.js --mode production", 9 | "imgmin": "gulp imgmin", 10 | "sp": "gulp sprites", 11 | "webp": "gulp webp" 12 | }, 13 | "author": "", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "autoprefixer": "^9.0.1", 17 | "babel-core": "^6.26.3", 18 | "babel-loader": "^7.1.5", 19 | "babel-preset-latest": "^6.24.1", 20 | "clean-webpack-plugin": "^0.1.19", 21 | "copy-webpack-plugin": "^4.5.2", 22 | "css-loader": "^1.0.0", 23 | "ejs-html-loader": "^3.1.0", 24 | "ejs-render-loader": "^1.0.0", 25 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 26 | "file-loader": "^1.1.11", 27 | "gulp": "^3.9.1", 28 | "gulp-cache": "^1.0.2", 29 | "gulp-cheerio": "^0.6.3", 30 | "gulp-cssnano": "^2.1.3", 31 | "gulp-if": "^2.0.2", 32 | "gulp-imagemin": "^4.1.0", 33 | "gulp-webp": "^3.0.0", 34 | "gulp-webpcss": "^1.1.1", 35 | "gulp.spritesmith": "^6.9.0", 36 | "html-loader": "^0.5.5", 37 | "html-webpack-plugin": "^3.2.0", 38 | "html-withimg-loader": "^0.1.11", 39 | "imagemin-mozjpeg": "^7.0.0", 40 | "imagemin-pngquant": "^6.0.0", 41 | "jquery": "^3.3.1", 42 | "mini-css-extract-plugin": "^0.4.1", 43 | "node-sass": "^4.9.2", 44 | "optimize-css-assets-webpack-plugin": "^4.0.0", 45 | "postcss-loader": "^2.1.6", 46 | "pug-html-loader": "^1.1.5", 47 | "px2rem-loader": "^0.1.9", 48 | "raw-loader": "^0.5.1", 49 | "sass-loader": "^7.0.3", 50 | "style-loader": "^0.21.0", 51 | "uglifyjs-webpack-plugin": "^1.2.7", 52 | "url-loader": "^1.0.1", 53 | "vinyl-buffer": "^1.0.1", 54 | "vue": "^2.5.16", 55 | "vue-loader": "^15.2.6", 56 | "vue-template-compiler": "^2.5.16", 57 | "webpack": "^4.16.2", 58 | "webpack-cli": "^3.1.0", 59 | "webpack-dev-server": "^3.1.5", 60 | "webpack-merge": "^4.1.3", 61 | "webpcss": "^1.2.2" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /sprites-css.handlebars: -------------------------------------------------------------------------------- 1 | {{#sprites}} 2 | .icon-{{name}}, 3 | {{/sprites}} 4 | ._useless_{ 5 | background-image: url({{{spritesheet.image}}}); 6 | display: inline-block; 7 | } 8 | 9 | {{#sprites}} 10 | .icon-{{name}}{ 11 | background-position: {{px.offset_x}} {{px.offset_y}}; 12 | width: {{px.width}}; 13 | height: {{px.height}}; 14 | } 15 | {{/sprites}} -------------------------------------------------------------------------------- /src/assets/_img/AbsolutePath.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfyfly/webpack-multi-page/ba3d91e492f0b6afb877676ecce056a0a7d500ff/src/assets/_img/AbsolutePath.jpg -------------------------------------------------------------------------------- /src/assets/_img/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfyfly/webpack-multi-page/ba3d91e492f0b6afb877676ecce056a0a7d500ff/src/assets/_img/avatar.jpg -------------------------------------------------------------------------------- /src/assets/_img/pngmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfyfly/webpack-multi-page/ba3d91e492f0b6afb877676ecce056a0a7d500ff/src/assets/_img/pngmin.png -------------------------------------------------------------------------------- /src/assets/_img/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfyfly/webpack-multi-page/ba3d91e492f0b6afb877676ecce056a0a7d500ff/src/assets/_img/test.jpg -------------------------------------------------------------------------------- /src/assets/img/AbsolutePath.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfyfly/webpack-multi-page/ba3d91e492f0b6afb877676ecce056a0a7d500ff/src/assets/img/AbsolutePath.jpg -------------------------------------------------------------------------------- /src/assets/img/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfyfly/webpack-multi-page/ba3d91e492f0b6afb877676ecce056a0a7d500ff/src/assets/img/avatar.jpg -------------------------------------------------------------------------------- /src/assets/img/pngmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfyfly/webpack-multi-page/ba3d91e492f0b6afb877676ecce056a0a7d500ff/src/assets/img/pngmin.png -------------------------------------------------------------------------------- /src/assets/img/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfyfly/webpack-multi-page/ba3d91e492f0b6afb877676ecce056a0a7d500ff/src/assets/img/test.jpg -------------------------------------------------------------------------------- /src/assets/sprites/icons.css: -------------------------------------------------------------------------------- 1 | .icon-a, 2 | .icon-b, 3 | .icon-c, 4 | .icon-d, 5 | ._useless_{ 6 | background-image: url(./icons.png); 7 | display: inline-block; 8 | } 9 | 10 | .icon-a{ 11 | background-position: 0px 0px; 12 | width: 160px; 13 | height: 160px; 14 | } 15 | .icon-b{ 16 | background-position: -160px 0px; 17 | width: 160px; 18 | height: 160px; 19 | } 20 | .icon-c{ 21 | background-position: 0px -160px; 22 | width: 160px; 23 | height: 160px; 24 | } 25 | .icon-d{ 26 | background-position: -160px -160px; 27 | width: 160px; 28 | height: 160px; 29 | } 30 | -------------------------------------------------------------------------------- /src/assets/sprites/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfyfly/webpack-multi-page/ba3d91e492f0b6afb877676ecce056a0a7d500ff/src/assets/sprites/icons.png -------------------------------------------------------------------------------- /src/common_css_entry.js: -------------------------------------------------------------------------------- 1 | // 只因用样式文件 2 | import '@/css/_common/common.scss' 3 | -------------------------------------------------------------------------------- /src/css/_common/common.scss: -------------------------------------------------------------------------------- 1 | body { 2 | color: $global-color; 3 | } -------------------------------------------------------------------------------- /src/css/index/index.css: -------------------------------------------------------------------------------- 1 | body{ 2 | transform: translateX(100px); 3 | } 4 | 5 | -------------------------------------------------------------------------------- /src/css/index/index.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background: url(~@/assets/img/test.jpg); 3 | img{ 4 | width: 100px; 5 | } 6 | } -------------------------------------------------------------------------------- /src/css/test/test.scss: -------------------------------------------------------------------------------- 1 | $color: #ccc; 2 | body { 3 | color: $color; 4 | background: url(~@/assets/img/test.jpg); 5 | img{ 6 | width: 100px; 7 | } 8 | } -------------------------------------------------------------------------------- /src/global.scss: -------------------------------------------------------------------------------- 1 | $global-color:yellow; -------------------------------------------------------------------------------- /src/js/_modules/P.js: -------------------------------------------------------------------------------- 1 | 2 | console.log('from module P') 3 | export default class P{ 4 | constructor(name){ 5 | this.name =name 6 | } 7 | getName(){ 8 | console.log(this.name) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/js/_modules/async.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | async: true 3 | } -------------------------------------------------------------------------------- /src/js/a.js: -------------------------------------------------------------------------------- 1 | console.log('a') -------------------------------------------------------------------------------- /src/js/about.js: -------------------------------------------------------------------------------- 1 | console.log('about') 2 | import P from './_modules/P' 3 | var p = new P('fly') 4 | p.getName() 5 | console.log(process.env.NODE_ENV) 6 | var $ =require('jquery') 7 | console.log($) -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | console.log('index') 2 | import P from './_modules/P' 3 | import '@/css/index/index.css' 4 | import '@/css/index/index.scss' 5 | import '@/assets/sprites/icons.css' 6 | 7 | var p = new P('fly') 8 | p.getName() 9 | console.log(process.env.NODE_ENV) 10 | var $ = require('jquery') 11 | console.log($) 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/js/index1.js: -------------------------------------------------------------------------------- 1 | console.log('index') 2 | import P from './_modules/P' 3 | import '@/css/index/index.css' 4 | import '@/css/index/index.scss' 5 | var p = new P('fly') 6 | p.getName() 7 | console.log(process.env.NODE_ENV) 8 | var $ = require('jquery') 9 | console.log($) 10 | 11 | // 按需加载,懒加载 12 | // document.onclick = function () { 13 | // require.ensure(['./_modules/async'], function (require) { 14 | // let a = require('./_modules/async'); 15 | // console.log(a) 16 | // }); 17 | // } 18 | 19 | -------------------------------------------------------------------------------- /src/js/t.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfyfly/webpack-multi-page/ba3d91e492f0b6afb877676ecce056a0a7d500ff/src/js/t.js -------------------------------------------------------------------------------- /src/js/test/test-0.js: -------------------------------------------------------------------------------- 1 | import '@/css/test/test.scss' 2 | console.log('test-0') 3 | -------------------------------------------------------------------------------- /src/js/test/test-1.js: -------------------------------------------------------------------------------- 1 | import '@/css/test/test.scss' 2 | console.log('test-1') 3 | -------------------------------------------------------------------------------- /src/js/vue.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './vue/App.vue' 3 | 4 | Vue.config.productionTip = false 5 | 6 | new Vue({ 7 | el: '#app', 8 | components: { App }, 9 | template: '' 10 | }) 11 | -------------------------------------------------------------------------------- /src/js/vue/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /src/pages/_tpls/a.pug: -------------------------------------------------------------------------------- 1 | h1 pug -------------------------------------------------------------------------------- /src/pages/a.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | a 8 | 9 | 10 |

a

11 | 12 | -------------------------------------------------------------------------------- /src/pages/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | about 8 | 9 | 10 |

about

11 | to index 12 | 13 | -------------------------------------------------------------------------------- /src/pages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | html 8 | 9 | 10 | 11 |

html

12 | to about 13 |
14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |
24 | 25 | -------------------------------------------------------------------------------- /src/pages/index1.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | meta(charset="UTF-8") 5 | meta(name="viewport", content="width=device-width, initial-scale=1.0") 6 | meta(http-equiv="X-UA-Compatible", content="ie=edge") 7 | title html 8 | body 9 | h1 html 10 | a(href="about.html") to about 11 | div 12 | img(src="../assets/img/avatar.jpg", alt="") 13 | img(src="/assets/img/AbsolutePath.jpg", alt="") 14 | include ./_tpls/a.pug 15 | -------------------------------------------------------------------------------- /src/pages/t.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/pages/test/test-0.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | test-0 二级目录页面 9 | 10 | 11 | 12 | 13 | 14 |

test-0

15 |

二级目录页面

16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /src/pages/test/test-1.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | meta(charset="UTF-8") 5 | meta(name="viewport", content="width=device-width, initial-scale=1.0") 6 | meta(http-equiv="X-UA-Compatible", content="ie=edge") 7 | title test-1 8 | base(href="../") 9 | body 10 | h1 test-1 11 | h2 二级目录 12 | a(href="about.html") to about 13 | div 14 | img(src="../../assets/img/avatar.jpg", alt="") 15 | img(src="/assets/img/AbsolutePath.jpg", alt="") 16 | include ../_tpls/a.pug 17 | -------------------------------------------------------------------------------- /src/pages/vue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | vue 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /src/readFileList.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const resolve = function (_path) { 4 | return path.resolve(__dirname, _path) 5 | } 6 | 7 | const targetPath = resolve('./pages') 8 | 9 | 10 | 11 | function getFileList(targetPath) { 12 | let fileList = [] 13 | const _getFileList = function (targetPath) { 14 | let dirFileList = fs.readdirSync(targetPath) 15 | return dirFileList.forEach(filename => { 16 | // 排除下划线开头的所有文件和文件夹 17 | if (/^_/.test(filename)) return 18 | let _path = path.resolve(targetPath, filename) 19 | if (fs.statSync(_path).isDirectory()) { 20 | _getFileList(_path) 21 | } else { 22 | fileList.push({path:_path,filename}) 23 | } 24 | }) 25 | } 26 | _getFileList(targetPath) 27 | return fileList 28 | } 29 | console.log(getFileList(targetPath)) 30 | // console.log(fileList) 31 | 32 | // 数组扁平化 排除undefined -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfyfly/webpack-multi-page/ba3d91e492f0b6afb877676ecce056a0a7d500ff/static/.gitkeep -------------------------------------------------------------------------------- /static/css/static.css: -------------------------------------------------------------------------------- 1 | body a{ 2 | color: aqua; 3 | } -------------------------------------------------------------------------------- /webpack.cfg.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | module.exports = { 3 | commonCss:{ 4 | entry: path.resolve(__dirname,'src/common_css_entry.js'), // String 必填,绝对地址 5 | exclude:['about'] // Arrary 排除页面,不填所有页面都引入common_css 6 | }, 7 | // px2rem:{ 8 | // remUni:100, 9 | // remPrecision: 6 10 | // }, 11 | dev: { 12 | assetsPublicPath: '/', // 资源公共路径 13 | proxy: { // 代理 14 | "/index/1.html": { 15 | target: "http://localhost:8080", 16 | pathRewrite: {"/index/1.html" : "/index1.html"} 17 | }, 18 | "/api": { 19 | target: "http://localhost:3000", 20 | changeOrigin:true, 21 | pathRewrite: {"^/api" : ""} 22 | } 23 | } 24 | }, 25 | build: { 26 | assetsPublicPath: './', // 也可是cdn地址 27 | assetsSubDirectory: 'static', // 打包后资源路径 28 | productionSourceMap: false // 打包生成sourceMap 29 | } 30 | } -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const HtmlWebpackPlugin = require('html-webpack-plugin') 3 | const webpack = require('webpack') 4 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 5 | const VueLoaderPlugin = require('vue-loader/lib/plugin') 6 | const { resolve, getEntries, getHtmlWebpackPlugins } = require('./webpack.until.js') 7 | const cfg = require('./webpack.cfg.js') 8 | let otherEntries = {} 9 | // 公共css文件入口 10 | if (cfg.commonCss && cfg.commonCss.entry) { 11 | otherEntries.common_css = cfg.commonCss.entry 12 | } 13 | 14 | module.exports = (env, argv) => { 15 | console.log(argv.mode, '========================') 16 | return { 17 | entry: { 18 | ...otherEntries, 19 | ...getEntries(argv) 20 | }, 21 | resolve: { //导入的时候不用写拓展名 22 | extensions: ['.js', '.vue', '.json'], 23 | }, 24 | resolve: { 25 | alias: { 26 | 'vue$': 'vue/dist/vue.esm.js', 27 | '@': resolve('src'), 28 | } 29 | }, 30 | output: { 31 | path: resolve('dist'), 32 | filename: `${cfg.build.assetsSubDirectory}/js/[name].[chunkhash].js`, 33 | // chunkFilename: `${cfg.build.assetsSubDirectory}/js/[name].[chunkhash].js`, 34 | publicPath: argv.mode === 'production' 35 | ? cfg.build.assetsPublicPath 36 | : cfg.dev.assetsPublicPath 37 | }, 38 | 39 | module: { 40 | rules: [ 41 | { 42 | test: /\.html$/, 43 | use: [ 44 | { 45 | loader: 'html-loader', 46 | options: { 47 | } 48 | }] 49 | }, 50 | { 51 | test: /\.pug$/, 52 | use: [ 53 | 'html-loader', 54 | // 'raw-loader', 55 | { 56 | loader: 'pug-html-loader', 57 | options: { 58 | data: { aa: 2222 } // set of data to pass to the pug render. 59 | } 60 | }] 61 | }, 62 | { 63 | test: /\.js$/, 64 | exclude: /(node_modules|bower_components)/, 65 | use: { 66 | loader: 'babel-loader', 67 | } 68 | }, 69 | { 70 | test: /\.vue$/, 71 | use: [ 72 | { 73 | loader: 'vue-loader', 74 | } 75 | ] 76 | }, 77 | { 78 | test: /\.(gif|png|jpe?g|svg)$/i, 79 | exclude: /(node_modules|bower_components)/, 80 | use: [ 81 | { 82 | loader: 'url-loader', 83 | options: { 84 | limit: 8 * 1024, 85 | name: `${cfg.build.assetsSubDirectory}/img/[name]-[hash:7].[ext]`, 86 | } 87 | } 88 | ] 89 | }, 90 | { 91 | test: /\.(woff|svg|eot|ttf)\??.*$/, 92 | exclude: /(node_modules|bower_components)/, 93 | use: { 94 | loader: 'url-loader', 95 | options: { 96 | name: `${cfg.build.assetsSubDirectory}/font/[name].[hash:7].[ext]`, 97 | limit: 8192 98 | } 99 | } 100 | }, 101 | ] 102 | }, 103 | 104 | plugins: [ 105 | new VueLoaderPlugin(), 106 | // new webpack.HotModuleReplacementPlugin(), // 启用 热更新 107 | ...getHtmlWebpackPlugins(argv), 108 | new webpack.LoaderOptionsPlugin({ 109 | options: { 110 | htmlLoader: { 111 | root: path.resolve(__dirname, './src') // 对于html中的绝对路径进行定位, /assets/a.jpg => path.resolve(__dirname, '/src/assets/a.jpg') 112 | } 113 | } 114 | }), 115 | ], 116 | } 117 | } -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | var webpackCommon = require('./webpack.common.js') 2 | const merge = require('webpack-merge') 3 | const path = require('path') 4 | const { styleLoader } = require('./webpack.until.js') 5 | const cfg = require('./webpack.cfg.js') 6 | 7 | module.exports = (env, argv) => { 8 | 9 | return merge(webpackCommon(env, argv), { 10 | mode: 'development', // 当mode值为'production'时,webpack-dev-server 变动刷新反应很慢 11 | devtool: 'cheap-module-eval-source-map', 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.(css|scss|sass)$/, 16 | use: ['style-loader'].concat(styleLoader) 17 | } 18 | ] 19 | }, 20 | devServer: { 21 | host:'0.0.0.0', 22 | proxy: cfg.dev.proxy 23 | } 24 | }) 25 | }; 26 | 27 | 28 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const webpackCommon = require('./webpack.common.js') 2 | const merge = require('webpack-merge') 3 | const CleanWebpackPlugin = require('clean-webpack-plugin') 4 | const CopyWebpackPlugin = require('copy-webpack-plugin') 5 | // const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 6 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 7 | const { styleLoader } = require('./webpack.until.js') 8 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') 9 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 10 | const webpack = require('webpack') 11 | const path = require('path') 12 | const cfg = require('./webpack.cfg.js') 13 | module.exports = (env, argv) => { 14 | return merge(webpackCommon(env, argv), { 15 | mode: 'production', // 当mode值为'production'时,webpack-dev-server 变动刷新反应很慢 16 | devtool: cfg.build.productionSourceMap ? '#source-map' : undefined, 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.(css|scss|sass)$/, 21 | use: ExtractTextPlugin.extract({ 22 | fallback: "style-loader", 23 | use: styleLoader, 24 | publicPath:'../../' 25 | }) 26 | }, 27 | ] 28 | }, 29 | plugins: [ 30 | new CleanWebpackPlugin('./dist'), 31 | new CopyWebpackPlugin([ 32 | { 33 | from: path.resolve(__dirname, cfg.build.assetsSubDirectory), 34 | to: cfg.build.assetsSubDirectory, 35 | ignore: ['.*'] 36 | } 37 | ]), 38 | 39 | // new MiniCssExtractPlugin({ 40 | new ExtractTextPlugin({ 41 | filename: `${cfg.build.assetsSubDirectory}/css/[name].[md5:contenthash:hex:20].css`, 42 | // filename: '[name].[md5:contenthash:hex:20].css', // 和html同目录是为了css相对路径起作用 43 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 44 | }), 45 | 46 | new UglifyJsPlugin({ 47 | uglifyOptions: { 48 | compress: { 49 | warnings: false 50 | } 51 | }, 52 | sourceMap: cfg.build.productionSourceMap, 53 | parallel: true 54 | }), 55 | new OptimizeCssAssetsPlugin({ 56 | cssProcessorOptions: { 57 | // sourcemap: cfg.build.productionSourceMap, 58 | map: cfg.build.productionSourceMap ? { 59 | inline: false, 60 | annotation: true 61 | } : undefined, 62 | autoprefixer: { disable: true }, 63 | cssProcessor: require('cssnano'), 64 | cssProcessorOptions: { safe: true, discardComments: { removeAll: true } }, 65 | canPrint: true 66 | } 67 | }), 68 | ], 69 | optimization: { 70 | runtimeChunk: { 71 | name: 'manifest', 72 | }, 73 | splitChunks: { 74 | minSize: 20000, // 超过20k才会被打包 75 | cacheGroups: { 76 | vendor: { 77 | name: "vendor", 78 | test: /[\\/]node_modules[\\/]/, 79 | chunks: "all", 80 | minChunks: 1 81 | }, 82 | commons: { 83 | name: "commons", 84 | chunks: "all", 85 | minChunks: 2 86 | } 87 | } 88 | } 89 | } 90 | }) 91 | } 92 | -------------------------------------------------------------------------------- /webpack.until.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | const resolve = function (_path) { 5 | return path.resolve(__dirname, _path) 6 | } 7 | const cfg = require('./webpack.cfg') 8 | 9 | // 判断当前页面是否包含CommonCssChunk 10 | function getCommonCssChunk(chunkName) { 11 | if (!cfg.commonCss) return [] 12 | // 无commonCss.exclude,所有页面包含 13 | if(!cfg.commonCss.exclude) return 'common_css' 14 | // 有commonCss.exclude,不包含在该数组的页面引用 15 | if(cfg.commonCss.exclude && !cfg.commonCss.exclude.includes(chunkName)) return 'common_css' 16 | // 其他 17 | return [] 18 | } 19 | 20 | let until = { 21 | resolve, 22 | getFileList(targetPath) { 23 | let fileList = [] 24 | const _getFileList = function (targetPath) { 25 | let dirFileList = fs.readdirSync(targetPath) 26 | return dirFileList.forEach(filename => { 27 | // 排除下划线开头的所有文件和文件夹 28 | if (/^_/.test(filename)) return 29 | let _path = path.resolve(targetPath, filename) 30 | if (fs.statSync(_path).isDirectory()) { 31 | _getFileList(_path) 32 | } else { 33 | fileList.push({ filepath: _path, filename }) 34 | } 35 | }) 36 | } 37 | _getFileList(targetPath) 38 | return fileList 39 | }, 40 | styleLoader: [ 41 | 'css-loader?sourceMap', // 将 CSS 转化成 CommonJS 模块 42 | 'postcss-loader?sourceMap'].concat( 43 | cfg.px2rem ? { 44 | loader: 'px2rem-loader', 45 | options: cfg.px2rem 46 | } : [], 47 | { 48 | loader: 'sass-loader', 49 | options: { 50 | sourceMap: true, 51 | data: '@import "src/global.scss";' 52 | } 53 | } 54 | ), 55 | getEntries(argv) { 56 | 57 | let entries = until.getFileList(resolve('./src/js')) 58 | let entry = {} 59 | let key 60 | entries.forEach((file) => { 61 | if (/.js$/.test(file.filename)) { 62 | key = file.filename.replace(/.js$/, '') 63 | entry[key] = file.filepath 64 | } 65 | }) 66 | console.log('【入口文件】') 67 | console.log(entry) 68 | return entry 69 | }, 70 | getHtmlWebpackPlugins(argv) { 71 | let targetPath = resolve('./src/pages') 72 | let htmls = until.getFileList(targetPath) 73 | let HtmlWebpackPlugins = [] 74 | htmls.forEach((file) => { 75 | let chunkName 76 | var reg = /\.[^.]+$/ 77 | if (reg.test(file.filename)) { 78 | chunkName = file.filename.replace(reg, '') 79 | console.log('.' + file.filepath.replace(targetPath, '').replace(reg, '.html')) 80 | HtmlWebpackPlugins.push( 81 | new HtmlWebpackPlugin({ 82 | baseTagUrl: '../', 83 | template: file.filepath, 84 | filename: '.' + file.filepath.replace(targetPath, '').replace(reg, '.html'), 85 | chunks: [chunkName].concat(getCommonCssChunk(chunkName)).concat(argv.mode === 'production' ? ['vendor', 'commons', 'manifest'] : []), 86 | inject: true, 87 | minify: argv.mode !== 'production' ? undefined : { 88 | removeComments: true, 89 | collapseWhitespace: true, 90 | removeAttributeQuotes: true, 91 | minifyCSS: true, 92 | minifyJS: true, 93 | // more options: 94 | // https://github.com/kangax/html-minifier#options-quick-reference 95 | }, 96 | }) 97 | ) 98 | } 99 | }) 100 | return HtmlWebpackPlugins 101 | } 102 | } 103 | 104 | 105 | module.exports = until --------------------------------------------------------------------------------