之前只知道webpack很强大,但是一直没有深入学习过,这次从头看了一下教程,然后从0开始搭建了一个多入口网站的开发脚手架,期间遇到过很多问题,所以有心整理一下,希望能给大家一点帮助。

多HTML网站使用webpack的必要性

假如我们接到这样一个任务,开发一个简单的官网,比如只有十几个html页面。项目很简单,我们没有必要使用什么大型框架,但是如果只是传统的写几个html、js和css,肯定会遇到这几个问题:

  • 网站导航和底部通栏是每个页面都共有的,如何实现复用?如果不复用,那么有改动的时候就要改n个页面,未免太傻
  • 如何在更改后强制清空用户缓存?我们不可能要求用户手动去清除浏览器缓存,那样太傻
  • 我想使用ES6进行js的开发,如何解决浏览器兼容性问题?
  • 我想使用less进行css样式开发,如何转换?

可以看出,没有自动化打包工具的加入,这些问题我们是很难解决的,因此使用webpack势在必行。

要实现的目标

看到这里,可能有的同学就急了,别废话,感觉进入正题吧,不!我们先定目标!无论做什么事情,都要先定目标,而不是干到哪里算哪里,这样是不会有大的提升的,正是在迈向目标的路上克服各种问题,我们才有进步,在进行这个脚手架搭建之前,我希望它是这样的

  • 能够打包成多个html文件和js文件,即支持多入口
  • 文件名称都要带上hash值,解决缓存问题
  • 能够复用网站的头部导航栏和底部通栏
  • 通过采用less进行样式的编写
  • 能够支持ES6开发
  • 用起来要方便,增加页面不需要手动去更改webpack的入口设置,希望能够根据目录下的文件自动配置
  • 不希望通过js动态插入css样式,这样会造成页面闪烁,希望html中直接引入css地址,就像平时开发那样
  • 能够实时看到开发的效果
  • build能够对代码进行压缩

好了,目标定了,开工

目录结构

别急,我们先来捋一捋目录,别着急写代码,一个好的目录,能让我们思路清晰,我的目录结构如下

+ config                    //环境变量配置文件,开发模式和生产模式使用不同的环境变量,比如接口地址,开发环境用的接口域名是http://a.com,生产环境使用的是http://b.com
- dev.env.js //本地开发变量
- prod.env.js //生产环境变量
+ src
+ css //自己的less组件或者第三方css库
+ component //自己组件的less
+ lib //第三方的css库,比如bootstrap
+ html //html代码,主要是一些模板,如头部导航,底部通栏,侧边栏
+ tpl //模板文件
+ img //图片文件
+ js //自己的js组件库或者第三方js库
+ mod //自己的js组件放这里
+ lib //第三方js库
+ page //页面文件
+ index //这个根据自己情况设置,有的页面相关性强,可以放到一个文件夹下,比如一个user文件夹,可以放个人中心的所有页面
- index.html //每个页面都要有一个html
- index.js //每个页面都要有一个js,名称和html的名称保持一致
- index.less //每个页面都要有一个同名less文件
+ test
- test.html
- test.js
- test.less + webpack //webpack的配置文件
- dev-server.js //开发服务设置,可以通过localhost访问页面,页面的实时编译
- webpack.common.js //开发环境和生产环境通用配置
- webpack.dev.js //开发环境特有的配置
- webpack.prod.js //生产环境特有的配置

首先是config目录,目前我主要放一些环境变量,就是开发环境和生产环境所不同的变量,比如接口地址,我们开发的时候,用本地的api接口地址,而打包的时候,要换成生产环境api地址。

webpack目录存放webpack的配置文件,其中开发和生产通用配置 放到webpack.common.js中,开发特有配置放到webpack.dev.js中,生产特有配置放到webpack.prod.js中。

src是我们开发的主目录,其中page目录放置我们的页面文件,这里可能和平时有所不同,我把每个页面用到的html、js和less文件放到了一起,有的同学可能把所有html放到一个目录下,js放到一个目录下,但是这样存在一个问题,每次改动页面,都要去翻目录,非常的不方便,我们应该把这种高度相关的文件放到一起,而提取的各种css组件或js组件可以和页面分开放置。

github地址:https://github.com/501351981/...

多入口配置

webpack支持多入口,即给定多个入口js文件,可以输出多个js文件,那么html怎么办呢?我希望开发过程是这样的,我在html中设置标题、SEO等信息,编写HTML内容代码,webpack把相关的js文件自动插入到html底部就行,可以的,这需要用到html-webpack-plugin 插件,可以通过调用html模板文件打包最终html。

安装html-webpack-plugin

npm install --save-dev html-webpack-plugin

webpack.common.js中多入口配置

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports={

    entry:[
index:'../src/page/index/index.js',
test:'../src/page/test/test.js'
], output: {
filename: '[name].[hash].js', //输出名称后面跟哈希值,解决缓存问题
path: path.resolve(__dirname,'../dist')
}, .... plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: '../src/page/index/index.html',
chunks: ['index'],
}) new HtmlWebpackPlugin({
filename: 'test.html',
template: '../src/page/test/test.html',
chunks: ['test'],
})
]
}

这样设置存在一个问题,每次新增一个页面,我就要到这里添加一下,未免很麻烦,我们其实可以通过读取 src/page下的js文件,自动加入入口配置;读取 src/page下的所有html文件,自动调用new HtmlWebpackPlugin进行实例化。

读取目录下所有文件名,我们需要引入glob,先安装

npm install --save-dev glob

改进后的配置

const glob = require('glob')
const CleanWebpackPlugin = require('clean-webpack-plugin'); //多入口js的配置,读取src/page下所有的js文件
function entries() {
let jsDir = path.resolve(__dirname, '../src/page')
let entryFiles = glob.sync(jsDir + '/**/*.js')
let map = {}; for (let i = 0; i < entryFiles.length; i++) {
let filePath = entryFiles[i];
let filename = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.'));
map[filename] = filePath;
}
return map;
} //读取多个html模板,进行插件实例化
function newHtmlWebpackPlugins(){
let jsDir = path.resolve(__dirname, '../src/page')
let htmls = glob.sync(jsDir + '/**/*.html')
let plugins=[] for (let i = 0; i < htmls.length; i++) {
let filePath = htmls[i];
let filename_no_extension = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.'));
let filename=filename_no_extension.concat('.html')
plugins.push(new HtmlWebpackPlugin({
filename: filename,
template: filePath,
chunks: [filename_no_extension],
}))
} return plugins
} module.exports={ entry:entries(), output: {
filename: '[name].[hash].js',
path: path.resolve(__dirname,'../dist')
}, .... plugins: [
...newHtmlWebpackPlugins()
]
}

好了,现在新增页面不需要更改webpack配置了,只需要重新运行一下 npm run start即可

共有头部和底部的复用

头部导航和底部通栏我们各个页面都是一样的,因此需要引入,那么html中怎么引入另一个html呢,这需要用到raw-loader 或 html-withimg-loader

安装raw-loader,raw-loader可以加载文件原始内容(utf-8格式)

npm install --save-dev raw-loader
//目录结构

+ src                       

    + html
+ tpl
- navbar.html //共用的头部导航
- footer.html //共用的底部导航
+ page //页面文件
+ index
- index.html + test
- test.html

我们在index.html中可以这么引用导航和底部通栏

<!--引入通用的导航部分-->
<%=require('raw-loader!../../html/tpl/navbar.html')%> <!--页面的正式内容在这里-->
<div id="app">
<p>首页的内容在这里</p>
</div> <!--引入通用的底部栏-->
<%=require('raw-loader!../../html/tpl/footer.html')%>

最初我在查找解决方案的时候,看到文章推荐使用raw-loader,但是发现这样存在一个问题,就是导航中无法引用本地的图片,比如导航中引用一个logo图片,是找不到的,因为我们打包的时候也会对图片进行处理,后面添加hash值,直接写图片路径是不行的,后来我改用 html-withimg-loader解决了

安装html-withimg-loader,顾名思义,这个插件可以加载带有图片的html

npm install --save-dev html-withimg-loader
<!--引入通用的导航部分-->
<%=require('html-withimg-loader!../../html/tpl/navbar.html')%> <!--页面的正式内容在这里-->
<div id="app">
<p>首页的内容在这里</p>
</div> <!--引入通用的底部栏-->
<%=require('html-withimg-loader!../../html/tpl/footer.html')%>

顺便提一句,html中引用图片地址是需要这样写的,需要通过require才行,简单的填写图片地址是不行的

<img src="${require('../../img/react.png')}" width="50" height="50">

支持ES6编写js

相信大家现在都已经学过ES6了,可是鉴于浏览器的兼容性,还没法随心所欲的用,需要插件支持,我们首先安装

npm install --save-dev babel-loader babel-core babel-preset-env

添加webpack配置

webpack.common.js,我们只对src目录下的js进行转换

{
test: /\.js$/,
use: {
loader: 'babel-loader'
},
include: path.resolve(__dirname,'../src')
},

同时在项目目录下添加一个名为.babelrc的文件,对babel进行设置,支持占有率大于1%的浏览器的最近2个版本

{
"presets": [
["env",{
"targets": {
"browsers": ["> 1%", "last 2 versions"]
}
}]
],
}

babel只是将ES6语法转为ES5的语法,比如箭头函数转为function(){},但是对一些ES6特有的功能没有转换,比如new Map(),打包之后还是new Map(),我们还需要再安装一个插件,完成这个转换工作。

npm install --save-dev babel-plugin-transform-runtime

更改.babelrc文件

{
"presets": [
["env",{
"targets": {
"browsers": ["> 1%", "last 2 versions"]
}
}]
],
"plugins": ["transform-runtime"] //引入插件
}

现在可以放心大胆的使用ES6了

使用Less编写样式

首先还是安装相关插件

npm install --save-dev less less-loader css-loader style-loader

webpack.common.js配置

{
test: /\.css$/,
use:["style-loader","css-loader","less-loader"]
},

在index.js文件中,我们就可以这样引入less文件了

import './index.less'

打包之后,运行html页面,index.js会动态把css样式插入到html页面,这样会造成一个问题,刚加载html的时候是一个样式,js插入css样式后是另一个样式,造成页面闪烁一下,体验不好(技术也要追求用户体验啊,不光是产品经理的事)。这有两个解决方案吧,第一个就是在JS未加载完成之前,显示一个loading动画,把整个页面遮盖住,第二个就是把css文件路径打包进html中,不要通过js动态添加,我选用的第二个方案。

我们要把less文件打包到一个css文件中,需要用到插件extract-text-webpack-plugin

npm install --save-dev extract-text-webpack-plugin

webpack.common.js

const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports={
rules: [
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader!less-loader"
}) },
]
} plugins: [
new ExtractTextPlugin("[name].[hash:8].css"),
]

这样打包之后的html中会引入css文件,类似这样


<link href="index.5eb2501d.css" rel="stylesheet">

webpack配置

实际在我从0开始搭建的过程中,是先进行webpack这块的配置的,之所以放到最后是不想影响主干内容,下面我们也简单介绍一下我的webpack配置。

webpack官方推荐不写重复的配置,即把本地和生产环境共用的配置放到一个文件,然后通过merge进行合并

webpack.dev.js

const webpack = require('webpack');
const merge = require('webpack-merge');
const common = require('./webpack.common'); var OpenBrowserPlugin = require('open-browser-webpack-plugin'); const env=require("../config/dev.env") module.exports=merge(common,{
mode:"development",
devtool: 'inline-source-map',
plugins:[
new webpack.DefinePlugin({
'process.env': env
}),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
new OpenBrowserPlugin({ url: 'http://localhost:5000' })
],
})

我们可以看到,通过webpack-merge插件,将共用配置webpack.common.js和开发的配置进行合并

new webpack.DefinePlugin({
'process.env': env
}),

DefinePlugin定义了全局变量process.env

new OpenBrowserPlugin({ url: 'http://localhost:5000' })

这个插件是为了在我们允许npm run start后,自动打开页面http://localhost:5000,避免每次都手动打开。
webpack-dev-server 为我们提供了一个简单的 web 服务器,并且能够实时重新加载,让我们可以实时看到开发结果,关于web服务器的配置,我放到了dev-server.js中

const webpackDevServer = require('webpack-dev-server');
const webpack = require('webpack'); const config = require('./webpack.dev');
const options = {
contentBase: './dist',
hot: true,
host: 'localhost',
}; webpackDevServer.addDevServerEntrypoints(config, options);
const compiler = webpack(config);
const server = new webpackDevServer(compiler, options); server.listen(5000, 'localhost', () => {
console.log('dev server listening on port 5000');
});

在package.json中,我们添加两个脚本

"scripts": {
"start": "node webpack/dev-server.js",
"build": "npx webpack --config webpack/webpack.prod.js",
},

这样我们就可以在命令行输入两个命令

npm run start :进入开发模式

npm run build:打包生产环境代码

好了,基本上把我做的这个脚手架介绍完了,实际要理解还需要自己去试,看是一回事,做出来又是另一回事,给别人讲明白那就更不容易了,前端路漫漫,大家努力吧。

github地址:https://github.com/501351981/...

这个脚手架还不完善,不过基本够用了,后面我还会再做几个脚手架,比如结合vue进行多页面开发或移动端H5开发,有兴趣可以持续关注。

使用webpack从0搭建多入口网站脚手架,可复用导航栏/底部通栏/侧边栏,根据页面文件自动更改配置,支持ES6/Less的更多相关文章

  1. 使用bootstrap3.0搭建一个具有自定义风格的侧边导航栏

    由于工作变动,新的项目组,可能会涉及到更多的类似于后台管理系统这一类的项目,而且开发可能更加偏向于传统型的开发,希望今后能够在新的项目中能够用得上vuejs吧! 接手项目的时候,就是一个后台管理系统, ...

  2. 小白一样能建站——winser2012 IIS8.0搭建基本的网站

    在window server 2012环境下,搭建一个基本的 网站.能够使用即可. 打开服务器管理器 添加角色和功能 默认下一步 下一步, 下一步,选择web服务器 添加功能 下一步, 下一步,不安装 ...

  3. swift 2.0 与 OC 相比较,标签栏和导航栏书写的差别

    下面是swift书写的时候的两个方法,其实这里不是教大家怎么样写的这个问题,我是想通过这两个不同语言的进行的一个比较,向大家找他们之间“想法”上的一些相同点,这样子我们学习swift的时候,就可以更加 ...

  4. 使用 webpack 搭建多入口项目

    闲来无事,学习一下怎么用 webpack 自定义多入口项目的打包 项目github地址:https://github.com/xiaoliwang2016/webpack-demo 先来看一下目录结构 ...

  5. 《前端之路》之 webpack 4.0+ 的应用构建

    目录 一.版本 二.webpack 的主体概念 2-1.入口 2-1-1.单页面入口 2-1-2.多页面应用的入口 2-2.输出 2-3.loader 2-4.plugins 三.如何使用 3-1 关 ...

  6. element-ui和npm、webpack、vue-cli搭建Vue项目

    一.element-ui的简单使用 1.安装 1. npm 安装 推荐使用 npm 的方式安装,它能更好地和 webpack 打包工具配合使用. npm i element-ui -S 2. CDN ...

  7. 如何利用webpack4.0搭建一个vue项目

    作为一个初学者,记录自己踩过的坑是个好的习惯.我本身比较懒,这里刚好有时间把自己的搭建过程记录一下这里是参考文章   https://www.jianshu.com/p/1fc5b5151abf文章里 ...

  8. express + mongodb 搭建一个简易网站 (三)

    express + mongodb 搭建一个简易网站 (三) 前面已经实现了基本的网站功能,现在我们就开始开搞一个完整的网站,现在整个网站的UI就是下面的这个样子. 我们网站的样子就照着这个来吧. 1 ...

  9. 最小白的webpack+react环境搭建

    本文也同步发表在我的公众号“我的天空” 从零开始,用最少的配置.最少的代码.最少的依赖来搭建一个最简单的webpack+react环境. 最近在玩webpack+react+移动端,那么第一步自然是搭 ...

随机推荐

  1. 基础篇六:Nginx编译配置参数

  2. screen小脚本

    # 创建screen,执行命令,最小化screen #!/usr/bin/env bash screen_name1=$"bdapi" # 检查screen是否存在,等于0.表示s ...

  3. VBA引用管理工具(可用于32、64位Office,Win7 Win10均可)

    VBAReferenceManager安装和使用方法,操作请看动态图: 下载地址: VBAReferenceManager.zip

  4. 65)PHP,跨脚本周期存变量(会话技术)

    (1)写入变量,常量,全局变量都不行, (2)试一下用文件存那个变量: 但是有一个问题:就是文件,只要一个用登陆成功了,其余的所有用户都可以直接用那个登录许可的标志了,然后只要用户名和密码登录成功,那 ...

  5. 3亿(int)数据-2亿(int)数据 求差集

    两个大文本,每行一条int数据 3亿(int)数据-2亿(int)数据 求差集 原始(粗暴)办法 1redis set 或类似方案 本地内存 cpu都撑不住 2持久化两张表 sql join mysq ...

  6. LeetCode Day 1

    目录 1.TwoSum 2.AddTwoNumbers 4.FindMedianSortedArrays 1.TwoSum 给定一个整数数组nums和一个目标值 target,请你在该数组中找出和为目 ...

  7. Spring:使用Spring AOP时,如何获取目标方法上的注解

    当使用spring AOP时,判断目标方法上的注解进行相关操作,如缓存,认证权限等 自定义注解 package com.agent.annotation; import java.lang.annot ...

  8. [rope大法好] STL里面的可持久化平衡树--rope

    简单用法: #include <ext/rope> using namespace __gnu_cxx; int a[1000]; rope<int> x; rope<i ...

  9. php数据库连接和mysql语句使用

    从简单的登录页开始学习. 前提:已经有一个html+css+js的静态网站 登录: php连接数据库,读取数据. <?php $username = root; $userpass = shao ...

  10. linux安装fasttext报错,升级gcc后成功解决

    首先说一下存在问题: 本人打算在linux安装一个fasttext用来训练词向量,本来是想要从gensim来调用fasttext的,但是加载大的本地txt一直不对,没办法了只好在linux安装一个fa ...