├── .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 |
2 |
3 |
from App.vue
4 | {{msg}}
5 |
6 |
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
--------------------------------------------------------------------------------