Project introduction
The procedure of this project is very detailed, anyone who follows the steps can reproduce 100%.
Blog adopts front and back end separation, front end is developed with VUE + TS +stylus, based on MVVM mode; The back end is developed with KOA2 +mysql+ Sequelize ORM, based on MVC pattern. The front and back ends are integrated by Webpack, and the production mode and development mode are separated configuration of Webpack. Finally, the front-end packaged DIST and back-end Server are uploaded to the server, and the front-end code is treated as a static resource on the back-end.
Due to the limited time before, the blog functions only login, registration, writing articles, modify articles, modify nicknames to change avatar, follow, comment, interested students can continue to add, such as reply, like, share, classification, label, recommendation and other functions. In addition to functionality, styles can be changed. Originally I was trying to imitate the nuggets, time was limited, did a few days did not write.
Blog Demo address
Making the address
The directory structure
Blog ├─.babelrc ├─.DockerIgnore ├─.Gitignore ├─Dockerfile ├─ Package-Lock. json ├─ Package.json ├─ TsConfig.json ├ ─ webpack.com mon. Js ├ ─ webpack. Dev. Js ├ ─ webpack. Prod. Js ├ ─ static | └ defaultAvatar. PNG ├ ─ client | ├ ─ App. Vue | ├ ─ index. The ts | ├ ─ the router. Ts | ├ ─ types | | └ vue. Which s | ├ ─ pages | | ├ ─ an Author. The vue | | ├ ─ Backend. Vue | | ├ ─ Blog. Vue | | ├ ─ Career. Vue | | ├ ─ Focus. Vue | | ├ ─ Freebie. Vue | | ├ ─ Frontend. Vue | | ├ ─ Home. Vue | | ├ ─ the Login. The vue | | ├ ─ the Register. The vue | | ├ ─ Search. Vue | | ├ ─ UserCenter. Vue | | └ Write. Vue | ├ ─ lib | | ├ ─ axiosInterceptor. Ts | | ├ ─ dateFormat, ts | | └ textFilter. Ts | ├ ─ config | | ├ ─ aliOss. Ts | | └ store. Ts | ├ ─ components | | ├ ─ AuthorCard. Vue | | ├ ─ Card. The vue | | ├ ─ CommentCard. Vue | | ├ ─ Header. The vue | | └ SearchCard. Vue ├ ─ server | ├ ─ app. Ts | ├ ─ the router. The ts | ├ ─ views | | └ index. The HTML | ├ ─ services | | ├ ─ BlogService. Ts | | ├ ─ CommentService. Ts | | ├ ─ FollowService. Ts | | ├ ─ ReplyService. Ts | | ├ ─ SortService. Ts | | └ UserService. Ts | ├ ─ public | | ├ ─ dist | ├ ─ models | | ├ ─ BlogModel. Ts | | ├ ─ CommentModel. Ts | | ├ ─ FollowModel. Ts | | ├ ─ ReplyModel. Ts | | ├ ─ SortModel. Ts | | └ UserModel. Ts | ├ ─ controllers | | ├ ─ BlogController. Ts | | ├ ─ CommentController. Ts | | ├ ─ FollowController. Ts | | ├ ─ SortController. Ts | | └ UserController. Ts | ├ ─ config | | ├ ─ the ts | | └ view ts ├ ─ node_modulesCopy the code
1. Initialize the project
npm init -y
npm i webpack webpack-cli --save-dev
Copy the code
2. Build the infrastructure – Install plug-ins
- 2.1 Automatically empty dist directory before compilation and install clean-webpack-plugin
npm i clean-webpack-plugin --save-dev
Copy the code
- 2.2 Implement automatic generation of final HTML from HTML template, install html-webpack-plugin
npm i html-webpack-plugin --save-dev
Copy the code
- 2.3 Configuring the typescript Environment and installing TS-Loader and typescript
npm i ts-loader typescript --save-dev
Copy the code
- 2.4 Set up a heat monitoring server for the development environment and install Webpack-dev-server
npm i webpack-dev-server --save-dev
Copy the code
- 2.5 Building a Project
| - client
| - node_modules
| - server
| - public
| - views
| - index.html
.gitignore
| - package-lock.json
| - package.json
| - README.md
Copy the code
3. Webpack configures the production environment and development environment
Create three configuration files, webpack.common.js, webpack.dev.js, and webpack.prod.js
- Webpack.com 3.1 mon. Js
const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
/ / the entry
entry: {
index: './client/index.ts'
},
// Compile the output configuration
output: {
// js generated to dist/js, [name] preserves the original JS file name and follows the generated chunkhash
filename: '[name]-[chunkhash:6].js'.// Output to server/public, dist, absolute path
path: path.resolve(__dirname, './server/public/dist')},/ / the plugin
plugins: [
new CleanWebpackPlugin(),
// Set the HTML template generation path
new HtmlWebpackPlugin({
filename: 'index.html'.template: './server/views/index.html'.chunks: ['index']})],// Configure the rules for each module
module: {
rules: [{test: /\.ts$/,
use: 'ts-loader'.exclude: /node_modules/}},// Configuration file extension
resolve: {
extensions: ['.ts'.'.js'.'.vue'.'.json']}}Copy the code
- 3.2 webpack. Dev. Js
const merge = require('webpack-merge');
const common = require('./webpack.common');
module.exports = merge(common, {
// Heat monitoring server, dynamic monitoring and real-time update page
devServer: {
contentBase: './server/public/dist'.// The default port is 8080
port: 8081.// Enable hot update
hot: true}});Copy the code
- 3.3 webpack. Prod. Js
const merge = require('webpack-merge');
const common = require('./webpack.common');
module.exports = merge(common, {
// Easy to trace source code errors
devtool: '#source-map'
});
Copy the code
- 3.4 modify the package. The json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"."build": "webpack --config webpack.prod.js --mode production"."dev": "webpack-dev-server --open chrome --config webpack.dev.js --mode development"
}
Copy the code
4. Switch from ES6 to ES5
- 4.1 Installing Babel series dependencies
npm install babel-loader @babel/core @babel/preset-env --save-dev
Copy the code
npm install @babel/plugin-transform-runtime @babel/plugin-transform-modules-commonjs --save-dev
Copy the code
npm install @babel/runtime --save
Copy the code
Note version compatibility: babel-loader8.x corresponds to babel-core7.x, and babel-loader7.x corresponds to babel-core6.x
- Change ES6 from webpack.common.js to ES5 at compile time:
module.exports = {
module: {
rules: [
// Process ES6 to ES5
{
test: /\.js$/,
use: {
loader: 'babel-loader'.options: {
presets: ['@babel/preset-env'].plugins: [
'@babel/plugin-transform-runtime'.'@babel/plugin-transform-modules-commonjs']}},exclude: /node_modules/}}}]Copy the code
5. Configure the VUE development environment
- 5.1 Installing vue-Loader, VUE, VUe-template-Compiler, and CSS-Loader
npm i vue-loader vue vue-template-compiler css-loader -S
Copy the code
- 5.2 configure webpack.com mon. Js
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
module: {
rules: [
/ / handle the vue
{
test: /\.vue$/,
use: 'vue-loader'}},plugins: [
Vue-loader must be used with VueLoaderPlugin, otherwise an error is reported
new VueLoaderPlugin()
]
}
Copy the code
In addition, if you introduce a.vue file in an entry file, it will be underlined in red because there is no declaration. So create a new types folder and create vue.d.ts in it:
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}
Copy the code
Because this project is developed in typescript, even with vue import/export declarations, the App. Vue file is not found. So create a new tsconfig.json file in the project root directory:
{
"compilerOptions": {
"target": "es5"."module": "commonjs"."sourceMap": true
},
"include": ["client"."server"]."exclude": ["node_modules"]}Copy the code
- 5.3 Record a pit
The following error is reported when starting webpack:
ERROR in chunk index [entry]
[name]-[chunkhash:6].js
Cannot use [chunkhash] or [contenthash] for chunk in '[name]-[chunkhash:6].js' (use [hash] instead)
Copy the code
This is because it is written this way when configuring webpack to output filename, so it simply uses hash.
6. Use stylus in vue
- 6.1 Installing dependency Packages
npm install style-loader --save-dev
Copy the code
npm install stylus-loader stylus --save-dev
Copy the code
- 6.2 Configuration in WebPack.common. js
module.exports = {
module: {
rules: [
// Handle CSS(similar to pipes, CSS -loader is preferred, style-loader is the last)
{
test: /\.css$/,
use: ['style-loader'.'css-loader']},/ / deal with stylus
{
test: /\.styl(us)$/,
use: ['style-loader'.'css-loader'.'stylus-loader'}]}}Copy the code
Note that restart the project every time you modify webPack.
- 6.3 Now we want to introduce styles via link
Install MiniCssExtractPlugin first:
npm i mini-css-extract-plugin --save-dev
Copy the code
Modify webpack.common.js and replace style-loader with webpack.common.js:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [...// Pull styles away and use link to import
new MiniCssExtractPlugin({
filename: '[name]-[hash:6].css'})].// Configure the rules for each module
module: {
rules: [...// Handle CSS(similar to pipes, CSS -loader is preferred, style-loader is the last)
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']},/ / deal with stylus
{
test: /\.styl(us)$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'.'stylus-loader'}]}}Copy the code
7. Manipulate image resources
-
7.1 Installing plug-ins file-loader and url-loader. Url-loader is based on file-loader, so both should be installed. (You can also only use file-loader. Url-loader extends the function on the basis of file-loader, such as setting the base64 transcoding of images smaller than the number of KB.)
npm install file-loader url-loader --save-dev Copy the code
-
7.2 configure webpack.com mon. Js
module.exports = {
module: {
rules: [
// Process the image
{
test: /\.(png|jpg|gif|eot|woff|ttf|svg|webp|PNG)$/,
loader: 'url-loader'.options: {
name: '[name]-[hash:6].[ext]'.esModule: false.// Otherwise the image load SRC is displayed as the Object Module
limit: 10240.// Base64 for special processing smaller than 10KB
},
exclude: /node_modules/}}}]Copy the code
8. Enable GZIP compression on the front end
Gzip, short for GNUzip, is a file compression program that can compress files into a. Gz compressed package. And our front-end gzip compression optimization, is through the gzip compression program, compress the resources, so as to reduce the file size of the requested resources. ** Gzip compression ability is very strong, the compression strength can reach 70%.
- 8.1 installation compression will – webpack – the plugin
npm i compression-webpack-plugin -D
Copy the code
- 8.2 Configuration in webpack.common.js
const CompressionWebpackPlugin = require('compression-webpack-plugin');
module.exports = {
plugins: [...new CompressionWebpackPlugin({
test: /\.(js|css)$/,
threshold: 10240 // Compress js and CSS files larger than 10K}})]Copy the code
Note: the use of compression-webpack-plugin will be affected by the version. If the version is too high, an error will be reported. Solution: Reinstall a lower version of the package
9. Use the At – the UI
At-ui is a front-end UI component library based on vue. js 2.0, which is mainly used for rapid development of background products in PC websites.
- 9.1 installation
npm i at-ui -S
Copy the code
Since the at-UI style is already a project in its own right, this is where NPM can install at-UI-style. Here I directly use the CDN method introduced to reduce overhead.
- 9.2 An Error Occurs After the packaging operation
ERROR in ./node_modules/element-ui/lib/theme-chalk/index.css
Module build failed (from ./node_modules/mini-css-extract-plugin/dist/loader.js):
ModuleParseError: Module parse failed: Unexpected character ' ' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
Copy the code
Solution: Replace url-loader with file-loader
// Process the image
{
test: /\.(png|jpg|gif|eot|woff|ttf|svg|webp|PNG)(\? \S*)? $/,
loader: 'file-loader'
// options: {
// name: '[name]-[hash:6].[ext]',
// esModule: false, // otherwise the image loading SRC is displayed as object Module
// limit: 10240, // Base64 for special processing smaller than 10KB
// puplicPath: './server/public'
// },
// exclude: /node_modules/
}
Copy the code
Create a navigation bar
- 10.1 Hide the Navigation Bar during Login or Registration
<header v-if="$route.name ! == 'register'"><header-section></header-section></header>
Copy the code
- 10.2 Password stored with bcrypt must be set to a sufficient length, otherwise false will always be returned.
11. Verify the login token
- 11.1 Installation Dependencies
npm i jsonwebtoken --save
npm i koa-jwt --save
Copy the code
- 11.2TS2304: Cannot find name ‘localStorage’
Configuration tsconfig. Json
{
"compilerOptions": {
"target": "es5"."module": "commonjs"."sourceMap": true."lib": ["DOM"."ES2016"."ES2015"]},"include": ["client"."server"]."exclude": ["node_modules"]}Copy the code
- 11.3 Authentication middleware must be placed before routes.
// Error handling
app.use(async (ctx, next) => {
return next().catch(err= > {
if(err.status === 401) {
ctx.status = 401;
ctx.body = 'Protected resource, use Authorization header to get access\n';
} else {
throwerr; }})});// Unless indicates that token verification is not performed for login registration (secret is used for token issuance)
app.use(koajwt({ secret: 'secret' }).unless({ path: [/^\/login/./^\/register/]})); app.use(bodyParser()); router(app);Copy the code
The front-end AXIos interceptor must add a token like this, otherwise koA-JWT will never parse successfully! Remember that! Remember that! I’ve been looking for a hole here all afternoon
let token = JSON.parse(localStorage.getItem('token'));
if(token) {
config.headers.common['Authorization'] = 'Bearer ' + token;
}
Copy the code
12. The user information in the header navigation bar is not refreshed after a successful login in the main component
- 12.1vuex结合localStorage
import Vuex from 'vuex';
import Vue from 'vue';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
user: JSON.parse(localStorage.getItem('user')) || null.token: JSON.parse(localStorage.getItem('token')) || ' '
},
getters: {
getUser: state= > state.user,
getToken: state= > state.token
},
mutations: {
setUser(state, payload) {
state.user = payload.user;
// Data persistence
localStorage.setItem('user'.JSON.stringify(payload.user));
},
setToken(state, payload) {
state.token = payload.token;
localStorage.setItem('token'.JSON.stringify(payload.token));
},
logout(state) {
localStorage.removeItem('user');
localStorage.removeItem('token');
state.user = null;
state.token = ' '; }}});export default store;
Copy the code
- 12.2 Login Component Invoked after successful login
// Store user information
this.$store.commit('setUser', { user: res.data.user });
this.$store.commit('setToken', { token: res.data.token });
Copy the code
- 12.2 Invoked upon Logout
logout() {
this.$store.commit('logout');
window.location.reload();
}
Copy the code
Here, the vuex update navigation bar was not refreshed, so I added reload to manually refresh. Due to limited time, specific reasons will be left for later analysis.
- 12.3 Authentication Invocation Failure (For example, The Token Expires and the Browser Clears Login Information)
this.axios.get('/sort').then(res= > {
this.sorts = res.data;
}, err= > {
if(err.code === -1) { // Token authentication failed
this.$store.commit('logout');
this.$router.push({ name: 'home'}); }})Copy the code
In summary, vuEX and localStorage can save user information during login. ** Store data in vuex needs to be computed to update views synchronically, so keep that in mind! N the bugs found a day, try a variety of methods, is to find the reason put one link blog.csdn.net/wangshang13 ~ ~ * *…
13. Beautify the upload button with input and label in vue
- 13.1
<div class="img-modify"> <label for="input-img"> <at-button type="primary"> name="input-img" @change="fileHandler($event)" accept="image/*"> </div>Copy the code
.img-modify flex 9; label position absolute; input opacity 0; width 82px; 31.6 px height;Copy the code
- 13.2 Obtaining a File Object
fileHandler(e) {
let file = e.target.files[0];
}
Copy the code
14. The front end gets all followers’ blogs and processes asynchronous operations in batches
- 14.1 Problem Description
When I follow a few bloggers, I click follow in the navigation bar to get all their posts. I started out inside the for loop, but there was obviously a problem with that, because the for loop was synchronous code and I could only ever get the result of the last request, so I had to fix that.
- 14.2 solve
Because I’m using AXIos, which itself encapsulates the promise, AND AXIos provides a convenient all method for batching asynchronous request results. Start by defining a return promise array, tentatively named promiseAll. Then, after receiving all the asynchronous results, batch process the results in the callback function by calling axios’s provided all method. The specific code is as follows:
// Get all posts from followers
getFollowersBlogs() {
// All asynchronous requests are returned first
let promiseAll = this.followerList.map((item) = > {
return this.axios.get('/blog/email/' + item.follow_email);
});
// reprocess all callback results
this.axios.all(promiseAll).then(resArr= > {
resArr.forEach(res= > {
this.blogList = this.blogList.concat(res.data);
});
}, err= > {
if(err.code === -1) { // Token authentication failed
this.$Modal.info({
content: 'Login expired, please log in again! '
});
this.$store.commit('logout');
this.$router.push({ name: 'login'}); }}); }Copy the code
15. Blog needs to be improved
- Home page
- Comments ✔
- Delete articles
- Editing articles must be revised to make them effective
- give a like
- Search page – Highlight your bookmarks
- Front-end URL encryption. Vue with params parameter pass, afraid to refresh the page parameters lost. With Query, the parameters are displayed directly in the address bar. So consider the query encryption here. A method found online uses Base64 encryption. ✔
- feedback
- Click Enter ➤ to register and search
- Password change
16. The front-end implements Base64 encryption on the URL
- 16.1 installation js – base64
npm install --save js-base64
Copy the code
- 16.2 Used in ES6+, where the vUE instance is mounted for global use
// Introduce js-base64 url encryption
import { Base64 } from 'js-base64';
Vue.prototype.$Base64 = Base64;
Copy the code
Encrypt parameters:
this.$router.push({
name: 'search'.query: { keyword: this.$Base64.encode(this.searchValue) }
});
Copy the code
Decrypt parameters:
this.keyword = this.$Base64.decode(this.$route.query.keyword);
Copy the code
17. The AXIos GET request passes arguments like a POST
- 17.1 How to Write a GET Request
this.axios.get('/blogs/list', {
params: {
pageSize: this.page.pageSize,
currentPage: this.page.currentPage
}
}).then(res= > {
this.blogList = res.data;
}, err= > {
console.error(err);
});
Copy the code
- 17.2 Obtaining Parameters
let pageSize = Number(ctx.request.query.pageSize),
currentPage = Number(ctx.request.query.currentPage);
Copy the code
Caution Before database query, the parameter is changed to an integer. Otherwise, an error is reported.
18. Listen to the event of the login and registration password box Enter to register the login
- Listen for the return event of the last input field
<at-input v-model="checkPass" type="password" placeholder="Please confirm your password." size="large"
:maxlength="12" :minlength="6" @keyup.enter.native="register"></at-input>
Copy the code
19. The comments
- 19.1 Page Click the text field to display the comment button
Remember a pit for vuEX to get user information.
Because vuEX stores users and tokens, calculated attributes must be used when vuEX is used in VUE; otherwise, errors will be reported. In addition, when logging out, the user and token have been deleted, so pay special attention to the use of avatar.
avatar: function() { if(this.$store.getters.getUser ! = =null) { return this.$store.getters.getUser.avatar; } return null; }, user: function() { if(this.$store.getters.getUser ! = =null) { return this.$store.getters.getUser; } return null; } Copy the code
- 19.2 Obtaining blog Comments Requires obtaining the user name and profile picture. Therefore, the user table must be associated with the comment table
// Users and comments are one-to-many
UserModel2.hasMany(CommentModel);
CommentModel.belongsTo(UserModel2, { foreignKry: 'email' });
Copy the code
// Get the comments from the blog (to return the user's avatar and user name, you need to create a one-to-many relationship)
findBlogComments: async (blog_id) => {
return await CommentModel.findAll({
where: {
blog_id
},
include: [{
model: UserModel2,
attributes: ['username'.'avatar']]}})}Copy the code
The above query statement will report an error:
SequelizeDatabaseError: Unknown column ‘comment.userEmail’ in ‘field list’
Comment out association declarations:
// Users and comments are one-to-many
// UserModel2.hasMany(CommentModel);
CommentModel.belongsTo(UserModel2, { foreignKey: 'email'.targetKey: 'email' });
Copy the code
When a model is associated with another model, add a statement. Otherwise, an error will be reported.
Note:
- If type does not exist, it is directly expressed as a string, for example, ‘TIMESTAMP’.
- If you need to record the update time when updating a table field, use updateAt and set the default value and the corresponding field name.
- If the default value is not a literal value, a literal function can be used to represent it.
- TableName Specifies the name of the table, where u is the alias.
- When establishing an association, do not write targetKey if the foreign key is associated with the primary key; otherwise, do not write targetKey.
Twenty.
The blog took me four days to set up, mainly to consolidate webPack configurations and to learn how to use typescript (although I didn’t use TS syntax very much). The whole process is very helpful for me to master the rapid construction of the project. I hope that my friends who are just beginners of the front end can also learn the use of Webpack through this process.